/*  $Id$

    Author:        Erik Flister
    E-mail:        erik.flister@gmail.com
	WWW:		   http://www.biology.ucsd.edu/labs/reinagel/eflister/
    Copyright (C): 2009, Erik Flister

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    As a special exception, if you link this library with other files,
    compiled with a Free Software compiler, to produce an executable, this
    library does not by itself cause the resulting executable to be covered
    by the GNU General Public License. This exception does not however
    invalidate any other reasons why the executable file might be covered by
    the GNU General Public License.
*/

:- module(theory,[
	% hardcoded constants
		numNotesPerOctave/1,
		whiteKeys/1,
		accidentals/1,
		intervalNamesTypes/1,
		intervalQualities/2,
		chordQuality/2,
		chordQuality/3,
		chordInversion/2,
		scaleName/2,
		modeName/3,
		octaveBreaks/1,
		voice/3,
		refNoteNum/2,
		consonant/1,
		functions/1,

	% fundamental predicates dynamically generated from the constants and cached out to ground for extremely fast search
		accidental/2,
		whiteKey/2,
		chroma/1,
		octaveRange/2,
		note/1,
		noteNum/2,
		intervalQuality/3,
		interval/1,
		interval/4,
		interval/2,
		interval/3,
		intervalSize/2,
		chordChromaClasses/4,
		chordChromaSet/4,
		chordNoteNums/2,
		ranges/1,
		selectVoicing/1,
		% scalesOverChord/2, % currently disabled because the caching takes several minutes.  call private theory:slowScalesOverChord/2 with same spec.

	% predicates related to chromas, notes, and intervals
		raiseNumHalfSteps/3,
		notes_chromas_nums/3,
		all_enharmonics/2,
		enharmonic/2,
		enharmonicLists/2,
		intervals/3,
		intervals/2,
		intervalSort/2,
		intervalInversion/2,
		allSpellingsOfNoteNum/2,
		allNoteNums/1,
		allNoteSpellings/1,
		allSpellingsOfNote/2,
		allSpellingsOfChroma/2,
		allChromaSpellings/1,

	% predicates related to scales, modes, keys, and degrees
		scale/1,
		scale/3,
		% properScale/1, % TBD
		% primeScale/1,  % TBD
		mode/1,
		mode/2,
		mode/3,
		degree/3,
		key/1,

	% predicates related to chords, voices, and voice leading
		chord/4,
		numVoices/1,
		inRange/1,
		consonantOver/2,
		voiceLeadingOK/1,

	% predicates for functional interpretation of chords in keys
		function/1,
		function_roman/2,
		function/2,
		simplifyFunction/2,

	% utility predicates of general interest -- could move to separate library
		shiftList/3,
		cumsum/2,
		diff/2,
		duplicate/3,
		uniqueCombinations/3,
		between/2,
		reduce/2,
		rand_insert/3,
		insert/4,
		fast_is_set/1,
		romanNumeral/2,
		conforms/2,
		generate/3
	]).

/** <module> theory: music theory reasoning in prolog

this prolog module defines predicates for reasoning about music theoretical concepts such as notes, scales, modes, intervals, chords, species counterpoint, figured bass, functional progressions, and voice leading.  
it is designed to work with a front-end GUI in python with realtime midi output.

---+++ predicate directory

*|hardcoded constants|*

	numNotesPerOctave/1
	whiteKeys/1
	accidentals/1
	intervalNamesTypes/1
	intervalQualities/2
	chordQuality/2
	chordQuality/3
	chordInversion/2
	scaleName/2
	modeName/3
	octaveBreaks/1
	voice/3
	refNoteNum/2
	consonant/1
	functions/1
	
*|predicates dynamically generated from the constants and cached out to ground for fast search|*
	
	accidental/2
	whiteKey/2
	chroma/1
	octaveRange/2
	note/1
	noteNum/2
	intervalQuality/3
	interval/1
	interval/4
	interval/2
	interval/3
	intervalSize/2
	chordChromaClasses/4
	chordChromaSet/4
	chordNoteNums/2
	ranges/1
	selectVoicing/1
	
	scalesOverChord/2 (currently disabled because the caching takes several minutes.  call private theory:slowScalesOverChord/2 with same spec.  in release will be cached using qsave_program/2).

*|predicates related to chromas, notes, and intervals|*
	
	raiseNumHalfSteps/3
	notes_chromas_nums/3
	all_enharmonics/2
	enharmonic/2
	enharmonicLists/2
	intervals/3
	intervals/2
	intervalSort/2
	intervalInversion/2
	allSpellingsOfNoteNum/2
	allNoteNums/1
	allNoteSpellings/1
	allSpellingsOfNote/2
	allSpellingsOfChroma/2
	allChromaSpellings/1
	
*|predicates related to scales, modes, keys, and degrees|*

	scale/1
	scale/3
	mode/1
	mode/2
	mode/3
	degree/3
	key/1
	properScale/1
	primeScale/1
	
*|predicates related to chords, voices, and voice leading|*
	
	chord/4
	numVoices/1
	inRange/1
	consonantOver/2
	voiceLeadingOK/1
	
*|predicates for functional interpretation of chords in keys|*
	
	function/1
	function_roman/2
	function/2
	simplifyFunction/2

*|utility predicates of general interest|*

	shiftList/3
	cumsum/2
	diff/2
	duplicate/3
	uniqueCombinations/3
	between/2
	reduce/2
	rand_insert/3
	insert/4
	fast_is_set/1
	romanNumeral/2
	conforms/2
	generate/3
	
---+++ conventions

in prolog, arguments are described with the following:

| +	| must be fully instantiated to a term that satisfies the required argument type (think of the argument as input)	|
| -	| must be unbound (think of the argument as output)																	|
| ? | can be either input or output																						|

predicates have the following determinism indicators:

| det		| will succeed exactly once (assuming you haven't grounded arguments inconsistently and have instantiated requied arguments) and not leave any choice-points (typical: arithmetic)	|
| semidet	| will either fail or succeed deterministically -- won't leave any choice-points (typical: tests)																					|
| nondet	| no constraints on the number of times the predicate succeeds and whether or not it leaves choice-points (typical: generators)														|
| multi		| as nondet, but the predicate will succeed at least once (assuming you haven't grounded arguments inconsistently and have instantiated requied arguments)							|

---+++ viewing docmentaiton

these module docs can be auto-generated and viewed using the pldoc beta included in swipl via:
	==
	doc_server(A),['theory.pl'],doc_browser.
	==
or they can be output to static html files using swipl's [[ltx2htm][http://prolog.cs.vu.nl/git/pl-57x.git?a=tree;f=packages/ltx2htm;hb=HEAD]] (repository also downloaded, but not installed, if you choose to add packages in swipl's prepare script) - the git snapshot can be installed via:
	==
	autoconf
	autoheader
	./configure
	make
	sudo make install
	==
then =|cd|= to the latex2html directory and do (i believe these are no longer necessary):
	==
	ln -s pl.pl pldoc.pl
	ln -s pl.cmd pldoc.cmd
	==
then, in swipl, do (extension MUST be .tex):
	==
	['theory.pl'],doc_latex('theory.pl', 'theory.tex', public_only(false)).
	==
and finally, in a unix terminal:
	==
	latex2html theory.tex
	==	
and if you have a tex distribution (like teTeX or texlive from macports) you can make a pdf.
first, edit [swipl's git repository]/man/txt/Makefile so that TEXMF points to your tex distribution.  (macports' texlive would be TEXMF=/opt/local/share/texmf)
make sure the texmf dir has a tfm subdirectory (for macports' texlive you'll have to sudo mkdir it).
also, do 'which -a perl' and make sure it matches the path in pl-57x/man/correctindex -- you may have to change /usr/local/bin/perl to /usr/bin/perl (osx 10.5.7's perl) or /opt/local/bin/perl (macports' perl)
then, in [swipl's git repository]/man/txt/ do
	 sudo make install
	 cd dvi2tty
	 sudo make install
	 =
	[swipl's git repository]/man/runtex --pdf theory
	 =
	 
---+++ installation

*|[[swipl][http://www.swi-prolog.org/]] xplatform open source prolog|*

	* to install from source on osx (if for some reason you are unhappy with the binary installer -- theory.pl only needs 5.7 to generate docs):
		* install [[macports][http://www.macports.org/]]
		* install [[git][http://git-scm.com/]] 1.6 or better (either via binary installer or via macports: =|port install git|=)
		* =|sudo port install swi-prolog|= (installs swipl and dependencies to /opt/local/) -- if you already have an /opt/local/ from running the swipl binary installer, use =|sudo port -f install swi-prolog|= (see =|man port|=, may need to update $MANPATH to include /opt/local/share/man:/opt/local/man:/usr/share/man:/usr/local/share/man)
		* =|cd|= to the directory where you want to clone swipl's git repository
		* =|git clone git://www.swi-prolog.org/home/pl/git/pl-57x.git|= grabs the latest rev of swipl (check for name of [[dev branch][http://www.swi-prolog.org/git/]] -- note that you canNOT build from a snapshot taken via this web interface)
		* =|cd|= to the repository
		* =|./prepare|=
		* =|cp build.templ build|=
		* edit build:
			* uncomment lines mentioned for osx
			* if you used macports above, you probably want to change PREFIX=/opt/local and delete the older swipl directories in /opt/local/bin/ (otherwise installs to $HOME and can't find atomic_list_concat/2, which was called concat_atom/2 until 5.7.8)
			* change other things mentioned as [EDIT] as you like (note ltxhtm cannot be installed using the PKG macro)
			* it survives without junit even if you don't tell it not to install jpl
		* =|sudo ./build|=
		* to pick up future updates, =|cd|= to the repository, and:
			==
			make distclean
			git pull
			./prepare
			./build
			==
	* [[prolog syntax coloring for xcode][http://www.cs.kuleuven.be/~anton/xcode_prolog.html]]
	* [[learn prolog now][http://cs.union.edu/~striegnk/learn-prolog-now/lpnpage.php?pageid=online]] excellent tutorial -- where i learned!  only takes a couple hours and changes your life.

*|[[pyswip][http://code.google.com/p/pyswip/]] (bidirectional python interface to swipl)|*

	* on osx, python must be started like the following, or else it cannot find swipl's shared library:
		==
		env DYLD_INSERT_LIBRARIES="/opt/local/lib/swipl-5.7.4/lib/i386-darwin9.6.0/libpl.dylib" python
		==
		* i used to use =|DYLD_LIBRARY_PATH|= but then the dynamic linker will still fail on code that uses certain swipl libraries (random.pl)
		* [[macports installs swipl to /opt/local/][http://trac.macports.org/wiki/FAQ#defaultprefix]], which is not on the standard search path for python's CDLL(), which calls the underlying platform's dlopen(), which searces paths specified by LD_LIBRARY_PATH and DYLD_LIBRARY_PATH
			* it is not recommended to permanently set these envrionment variables (eg in .bash_profile or .cshrc) because they can be security exploits (search google for refs)

*|[[pyPortMidi][http://alumni.media.mit.edu/~harrison/code.html]] (python bindings for [[portmidi][http://portmedia.sourceforge.net/]] -- xplatform realtime midi i/o -- via [[pyrex][http://www.cosc.canterbury.ac.nz/greg.ewing/python/Pyrex/]])|*

	* the following is how i follow [[pyPortMidi's installation instructions][http://cratel.wichita.edu/cratel/cratel_pyportmidi]]:
		* according to portmidi readme, in portmidi directory, i did:
			==
			make -f pm_mac/Makefile.osx
			==
			* note pyPortMidi's readme says to use =|xcodebuild -project pm_mac.pbproj|= from inside pm_mac directory -- i didn't do this
		* according to pyrex INSTALL.txt, in pyrex directory, i did:
			==
			python setup.py install
			==
			* i did not edit Pyrex/Mac/DarwinSystem.py.  the os.environ line was at a much higher line number than 21 in this version.  it read: =|os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.3"|=
		* copy the following:
			* portmidi/porttime/porttime.h -> pyPortMidi
			* portmidi/pm_common/portmidi.h -> pyPortMidi
			* portmidi/pm_mac/libportmidi.a -> pyPortMidi/OSX
			* portmidi/porttime/libporttime.a -> pyPotMidi/OSX
		* changed line in pypm.pyx to =|while(Pm_Poll(self.midi) != pmNoError):|=
		* changed line in pyPortMidi's setup.py to =|libraries = ["portmidi", "porttime"],|=
		* in pyPortMidi directory: =|sudo python setup.py install|=
		* in pyPortMidi directory, use =|python test.py|= to test that midi i/o is working.  
			* to use a software synth on osx:
				* open Audio MIDI Setup (in /Applications/Utilities)
				* under MIDI devices, double click IAC Driver
				* check "Device is online"
				* expand for "More Information" and make sure there is a port with in and out connectors
			* you can also drive a midi interface to external hardware synths via this panel, or mingus now has [[fluidsynth][http://www.nongnu.org/fluid/]] built in.
			* i use [[logic pro][http://www.apple.com/logicstudio/]], but free options are [[vmk][http://www.fredrikolofsson.com/software/vmk16osx.sit]] (a soft keyboard for midi input) and [[simplesynth][http://notahat.com/downloads/SimpleSynth-1.0.zip]] (a soft synth for midi output)

*|[[lilypond][http://lilypond.org/web/]] high quality music notation.|*  
	
	* i installed with =|port install lilypond|=

*|[[python imaging library (PIL)][http://www.pythonware.com/products/pil/]]|*
	
	* used by python to show notation .png's generated in lilypond.  
	* i installed with =|port install py26-pil|=
	* i had to add the following to .bash_profile because i had manually installed python2.6 before installing macports:
		==
		export PYTHONPATH=/opt/local/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/
		==
		
---+++ general info

@author Erik Flister erik.flister@gmail.com

@version 0.5 (alpha - implemented predicates tested and trusted)

@see
* [[mingus][http://code.google.com/p/mingus/]] (music theory library in python)
* [[pyBass][http://www.biology.ucsd.edu/labs/reinagel/eflister/]] (python realtime jazz improviser that uses pyPortMidi and theory.pl via pyswip)
* [[Music Theory Workbench][http://www.mitpressjournals.org/doi/pdf/10.1162/014892699559977]] (automated common practice harmony analysis)
* references :
	* harmony books
		* [[aldwell and schachter][http://www.amazon.com/Harmony-Voice-Leading-Edward-Aldwell/dp/0155062425]] the authority
		* [[roig-francoli][http://www.amazon.com/Harmony-Context-Miguel-Roig-Francoli/dp/0072938595/ref=sr_1_1?ie=UTF8&s=books&qid=1232038418&sr=1-1]] also good, includes counterpoint
		* [[levine][http://www.amazon.com/Jazz-Theory-Book-Mark-Levine/dp/1883217040/ref=pd_bxgy_b_img_b]] jazz
	* wikipedia
		* [[tonality][http://en.wikipedia.org/wiki/Tonality]]
		* [[harmony][http://en.wikipedia.org/wiki/Harmony]]
		* [[diatonic function][http://en.wikipedia.org/wiki/Diatonic_function]]
		* [[figured bass][http://en.wikipedia.org/wiki/Figured_bass]]
		* [[species counterpoint][http://en.wikipedia.org/wiki/Counterpoint]]
		* [[chord][http://en.wikipedia.org/wiki/Chord_(music)]]
		* [[tonnetz][http://en.wikipedia.org/wiki/Tonnetz]]
		* [[music and mathematics][http://en.wikipedia.org/wiki/Music_and_mathematics]]		
		* [[pitch space][http://en.wikipedia.org/wiki/Pitch_space]]]
		* [[musical set theory][http://en.wikipedia.org/wiki/Musical_set_theory]]
		* [[diatonic set theory][http://en.wikipedia.org/wiki/Diatonic_set_theory]]
		* [[consonance and dissonance][http://en.wikipedia.org/wiki/Consonance_and_dissonance]]
		* [[limit][http://en.wikipedia.org/wiki/Limit_(music)]]
			* key insight: "Harry Partch, Ivor Darreg, and Ralph David Hill are among the many microtonalists to suggest that music has been slowly evolving to employ higher and higher harmonics in its constructs. In medieval music, only chords made of octaves and perfect fifths (involving relationships among the first 3 harmonics) were considered consonant. In the West, triadic harmony sprang up around the time of the Renaissance, and triads quickly became the fundamental building blocks of Western music. The major and minor thirds of these triads invoke relationships among the first 5 harmonics.  Around the turn of the 20th century, tetrads debuted as fundamental building blocks in African-American music. In conventional music theory pedagogy, these tetrads are usually explained as chains of major and minor thirds. However, they can also be explained as coming directly from relationships among harmonics greater than 5. For example, the dominant 7th chord in 12-ET approximates 4:5:6:7, while the Major 7th chord approximates 8:10:12:15.  This story is suggestive of evolution through a regime of punctuated equilibria, wherein the predominant technology of each epoch (e.g. triads) almost completely replaces that of previous epochs..."
		* [[harmonic major scale][http://en.wikipedia.org/wiki/Harmonic_major_scale]]
		* [[jazz scales][http://en.wikipedia.org/wiki/Jazz_scales]]
		* [[scale coherence][http://en.wikipedia.org/wiki/Coherence_(music_theory)]]
	* http://www.andymilne.dial.pipex.com/Scales.shtml defines proper and prime scales, nondiatonic theories, etc.  
		* melodic smoothness: proper means that no interval of degree N in a scale is smaller than an interval of degree M<N
		* harmonic utility: prime means that every note is part of at least one major or minor third, and no note has both a major and minor third (possibly equivalent to no chromatic semitones - eg both c and c#)
			* there are only 5 prime scales, all heptatonicgiven 12 note octaves - major, harmonic minor, melodic minor, harmonic major, double harmonic		
	* [[microtonal theory][http://tonalsoft.com/enc/encyclopedia.aspx]]
	* tymoczko geometery of voice leading papers
		* http://www.sciencemag.org/cgi/content/full/313/5783/72 
		* http://www.sciencemag.org/cgi/content/full/320/5874/328
		* http://www.sciencemag.org/cgi/content/full/sci;320/5874/346
	* pachet and roy papers on constraint logic programming for common practice harmony
		* http://www.csl.sony.fr/downloads/papers/2000/pachet-constraints2000.pdf review
		* http://www-poleia.lip6.fr/~fdp/MyPapers/BackTalk/ES95.final.ps.Z
		* http://www-poleia.lip6.fr/~fdp/MyPapers/BackTalk/contraintes-tools95.ps.Z

@compat 

* i test/run with osx 10.5.6, python 2.6, pyPortMidi 0.0.3, pyrex 0.9.8.5, portmidi 82, pyswip 0.2.2, swipl 5.7, lilypond 2.12.1, PIL 1.1.6
* trying the betas of [[pldoc][http://www.swi-prolog.org/packages/pldoc.html]] (documentation) and [[plunit][http://www.swi-prolog.org/packages/plunit.html]] (unit testing), which are swipl native but somewhat generally compatible?
* swipl (common libraries used, likely almost compatible in most prologs: apply, debug, lists, pairs, aggregate).  output of check/0 catches some, but not all library usages.

@copyright Erik Flister

@license GPL

@bug 
* voiceLeadingOK seems to sometimes allow simultaneous jumps greater than a third in more than 1 voice.  i think this is because it doesn't realize that interval/2 omits non-existent intervals like doulbe-augmented-fifths (eg btw c and gx)
* conforms/2 does not enforce minimum requirements!

@tbd 
* voicing needs to disallow R333, RR33, and R337 (ie strictly more roots than anything else when 5th omitted)
* find best key sig -- one with fewest accidentals and fewest violations.  if still need resolution, prefer flats.  this suggests a tonic (how determine mode?)...
* still more doc to write
* functional progressions
* cadences
* dissonance handling in voice leading
* find static solution to complete voiceleading transition matrix -- 30k^2 but sparse, takes about 4 sec per voicing to check the other 30k, so that's only 33 hours of computation, can save with qsave_program.  add visualization/navigation to the GUI that projects 4D chord vectors to 2D space, with directed arcs at allowed transitions -- grey out those that are not state compatible (due to jumps/dissonances waiting for resolution)
* input of scores w/harmony error detection
* include functions/figured bass/chord symbols in output to pyswip/lilypond
* extended chords
* species couterpoint
* proper/prime scales + theories on prime scales
* switch to clp(fd)
	* this is the finite domain constraint solver, which defines #=/2 and #</2, etc, bidirectional replacements for is/2 and </2, so it can more quickly eliminate most integers from consideration.
	* [[docs][http://gollem.science.uva.nl/SWI-Prolog/Manual/clpfd.html]]
	* [[best concrete english description i've seen][http://gollem.science.uva.nl/SWI-Prolog/mailinglist/archive/2008/q1/0147.html]]
	* [[wikipedia][http://en.wikipedia.org/wiki/Constraint_logic_programming]]
	* [[bartak's guide][http://kti.ms.mff.cuni.cz/~bartak/constraints/index.html]]
* redefine terms using library(record) to get named access to fields and library(error)/DCG's to do type checking
* remove append/3's (any other bad style?)
* for interactive graph navigator, we'll try python xdot http://code.google.com/p/jrfonseca/wiki/XDot 
	* tho also checkout java zgrviewer that has this cool 'bring and go' feature: http://zvtm.sourceforge.net/zgrviewer/video/videos.html#BG

*/

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% imports

:- use_module(library(clpfd)).
:- use_module(library(error)).
:- use_module(library(record)).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% all hardcoded facts/constants here

%%	numNotesPerOctave(?N:integer) is det.
%
%	number of half steps per octave.
% 
%	hardcoded to =12=
%
%	@param	N number of half steps per octave

numNotesPerOctave(12).

%%	whiteKeys(?L:pairs(key-value)) is det.
%
%	white key locations and names.  
%
%	hardcoded to =|[0-a,2-b,3-c,5-d,7-e,8-f,10-g]|=
%
%	@param	L key-value pairs where keys are unique integers >=0 representing half step intervals above the lowest and values are atoms representing note names (order unimportant)

whiteKeys([0-a,2-b,3-c,5-d,7-e,8-f,10-g]). %format verified by "consistency checks" below
% hmm, note there is a tiny bit of redundancy by having these as the cummulative sum -- redundant with the order.  minor issue, but i'm trying to be totally axiomatic here.  
% really want to label the "holes" 2-1-2-2-1-2-2, but this would complicate naming because we aren't labeling holes.  would eliminate need for numNotesPerOctave tho, because then it would be the sum...

%%	accidentals(?L:list) is det.
%
%	accidental definition. 
%
%	hardcoded to =|[bb,b,'',#,x]|=
%
%	@param	L list of unique atoms representing accidentals in order from lowest to highest, must be separated by exact halfsteps, must contain the empty string (''), which corresponds to natural (no accidental)

accidentals([bb,b,'',#,x]). %format verified by "consistency checks" below

%%	intervalNamesTypes(?L:list([degree,type])) is det.
%
%	interval degrees and types.
%
%	hardcoded to =|[[octave,perfect],[second,imperfect],[third,imperfect],[fourth,perfect],[fifth,perfect],[sixth,imperfect],[seventh,imperfect]]|=
%
%	@param	L list of length-2 lists, where each sublist is [degree,type], in order from lowest to highest, must be same length as whiteKeys, degrees must be unique and contain one =|fifth|=, types in =|[perfect, imperfect]|=

intervalNamesTypes([[octave,perfect],[second,imperfect],[third,imperfect],[fourth,perfect],[fifth,perfect],[sixth,imperfect],[seventh,imperfect]]). %format verified by "consistency checks" below

%%	intervalQualities(?Type, ?Qualities:list) is multi.
%
%	interval types and qualities.
%
%	hardcoded so perfect->=|[diminished,perfect,augmented]|= and imperfect->=|[diminished,minor,major,augmented]|=
%
%	@param	Type		interval type atom in =|[perfect, imperfect]|=
%
%	@param	Qualities	list of unique qualities corresponding to Type, in order from lowest to highest, separated by exact halfsteps (perfect must contain perfect, and imperfect must contain major, which both correspond to natural)

intervalQualities(perfect,[diminished,perfect,augmented]).  %format verified by "consistency checks" below
intervalQualities(imperfect,[diminished,minor,major,augmented]).  %format verified by "consistency checks" below

%%	chordQuality(?Intervals:list([quality,degree]), ?Quality) is multi.
%
%	chord qualities and intervals
%
%	hardcoded intervalic content of triads and seventh chords.  
%   
%   ex: 
%   
%   ==
%   chordQuality([[minor,third],[diminished,fifth],[minor,seventh]],halfdiminished).
%   ==
%
%	@param	Intervals	list of length-2 lists, where each sublist is [quality,degree], sorted by interval size, all unique, none can be [perfect,octave]
%
%	@param	Quality		corresponding chord quality

chordQuality([[major,third],[perfect,fifth]],major).
chordQuality([[minor,third],[perfect,fifth]],minor).
chordQuality([[minor,third],[diminished,fifth]],diminished).
chordQuality([[major,third],[augmented,fifth]],augmented).

chordQuality([[major,third],[perfect,fifth],[major,seventh]],major).
chordQuality([[major,third],[perfect,fifth],[minor,seventh]],dominant).
chordQuality([[minor,third],[perfect,fifth],[minor,seventh]],minor).
chordQuality([[minor,third],[diminished,fifth],[minor,seventh]],halfdiminished).
chordQuality([[minor,third],[diminished,fifth],[diminished,seventh]],diminished).

%this allows us to remove the restrictions on the interval list of chordQuality/2, but chordQualityRelaxed(A,major) will run out of stack.
chordQualityRelaxed(L,Q):-
	chordQuality(U,Q),
	permutation(S,U),
	list_to_set(L,S).

%%	chordQuality(?Intervals:list([quality,degree]), ?Quality, ?Size) is multi.
%
%	chord qualities, intervals, and sizes
%
%	hardcoded sizes of triads and seventh chords.  
%   
%   ex: 
%   
%   ==
%   chordQuality([[minor,third],[diminished,fifth],[minor,seventh]],halfdiminished,seven).
%   ==
%
%	@param	Intervals	list of length-2 lists, where each sublist is [quality,degree], sorted by interval size, all unique, none can be [perfect,octave]
%
%	@param	Quality		corresponding chord quality
%
%	@param	Size		corresponding chord size in =|[triad,seven]|=

chordQuality(Intervals,Quality,Size):-
	chordQuality(Intervals,Quality),
	length(Intervals,N),
	((N is 2,Size=triad) ; (N is 3,Size=seven)).

%%	chordInversion(?Inversion, ?BassDegree) is multi.
%
%	chord inversion definitions
%
%	hardcodes names of inversions, for each degree of the bass over the root.
%   
%   ex: 
%   
%   ==
%   chordInversion(root,octave).
%   ==
%
%	@param Inversion name of the inversion in =|[root,first,second,third]|=
%
%	@param BassDegree degree of the bass over the root

% should add consistency checks to ensure that the BassDegrees appear in intervalsNamesTypes and chordQuality...
chordInversion(root,octave).
chordInversion(first,third).
chordInversion(second,fifth).
chordInversion(third,seventh).

%%	scaleName(?Intervals:list(integers), ?Name) is multi.
%
%	common scale definitions
%
%	definition of chromatic, diatonic, major, minor, harmonicMinor, and melodicMinor scales in terms of their intervalic content.
%   
%   ex: 
%   
%   ==
%   scaleName([2,2,1,2,2,2,1],major).
%   ==
%
%	@param	Intervals	list of >0 integers representing half steps between successive scale degrees, must sum to numNotesPerOctave
%
%	@param	Quality		corresponding chord quality

scaleName([1,1,1,1,1,1,1,1,1,1,1,1],chromatic).
scaleName([2,2,1,2,2,2,1],diatonic).
scaleName([2,2,1,2,2,2,1],major).
scaleName([2,1,2,2,1,2,2],minor). % natural, same as aeolian
scaleName([2,1,2,2,1,3,1],harmonicMinor). %mode 5 is spanish phrygian dominant or "jewish"
scaleName([2,1,2,2,2,2,1],melodicMinor). % ascending (descending same as aeolian)
scaleName([2,2,1,2,1,3,1],harmonicMajor).
scaleName([1,3,1,2,1,3,1],doubleHarmonic). % aka byzantine and arabic.  mode 4 is hungarian gypsy.  egyptian?
scaleName([2,1,2,1,2,1,2,1],wholeHalf).
scaleName([1,2,1,2,1,2,1,2],halfWhole).
scaleName([2,2,2,2,2,2],wholeTone).

%%	modeName(?Num:integer, ?Scale, ?ModeName) is multi.
%
%	common mode definitions
%
%	definition of the familiar diatonic church modes, plus the jazz modes of melodic and harmonic minor.  
%   
%   ex: 
%   
%   ==
%   modeName(1,[2, 2, 1, 2, 2, 2, 1],ionian).
%   ==
%
%	@param	Num			integer from 1 to numNotesPerOctave indicating the mode
%
%	@param	Scale		list of >0 integers representing half steps between successive scale degrees of the underlying scale, must sum to numNotesPerOctave
%
%	@param	ModeName	the mode name

% rework this to eliminate redundancy of listing I:  modeNames(diatonic,[ionian,dorian,phrygian,lydian,mixolydian,aeolian,locrian]).  any item can be a list to indicate alternate names?
modeName(I,S,N):-
	scaleName(S,diatonic),
	((I = 1, N = [ionian,major]);
	 (I = 2, N = dorian);
	 (I = 3, N = phrygian);
	 (I = 4, N = lydian);
	 (I = 5, N = mixolydian);
	 (I = 6, N = [aeolian,minor]);
	 (I = 7, N = locrian)).
modeName(I,S,N):-
	scaleName(S,melodicMinor),
	((I = 1, N = minMaj);				                        % min+7
	 (I = 2, N = [phrygianRaise6,dorianb9]);		                % susb9
	 (I = 3, N = lydianAugmented);			                    % maj7+5
	 (I = 4, N = lydianDominant);			                    % 7+11
	 (I = 5, N = mixolydianb13);			                    % 7b13
	 (I = 6, N = [halfDiminished,locrianRaise2,aeolianb5]);	    % min7b5
	 (I = 7, N = [altered,diminishedWholeTone,superLocrian])).	% 7alt  7#9b13
modeName(I,S,N):-
	scaleName(S,harmonicMinor),
	((I = 1, N = ?);
	 (I = 2, N = locrian13);
	 (I = 3, N = ionianShp5);
	 (I = 4, N = dorianShp11);
	 (I = 5, N = [mixolydianb9b13,phrygianMajor]);
	 (I = 6, N = lydianShp9);
	 (I = 7, N = locrianb4bb7)).

%%	octaveBreaks(?W:whiteKey) is det.
%
%	indicates the lowest white key of each octave number
% 
%	note the possible ambiguity of octave numbers for enharmonic notes.  we use the following convention:
%
% 	[bb0, a#0, cbb1], [b0, ax0, cb1], [c1, b#0, dbb1], [c#1, bx0, db1], d1, ..., b1, c2, d2, ...
%
%	hardcoded to =c=
%
%	@param	W the lowest whiteKey in each octave
octaveBreaks(c). %i'm not confident it's ok to change this.  lucky it seems to match lilypond.

%%	voice(?Name, ?Low:note, ?High:note) is multi.
%
%	voice definitions and ranges
%
%	hardcoded definition of voices and their ranges.  taken from roig-francoli, which is at slight variance from aldwell and schachter.  these are what limit the domain of notes so that all predicates are fast and finite, even with no arguments instantated. 
%
%	notes are =|[Chroma,Octave:integer]|=  (octave >= 1 -- i forget why, may no longer be required?)
%
%	chromas are =|[WhiteKeyName,Accidental]|=
%
%	names are in =|[bass,tenor,alto,soprano]|=
%
%	ex: 
%
%   ==
%   voice(bass,[[e,''],1],[[g,''],2]).
%   ==
%
%	@param	Name	name of voice
%
%	@param	Low		low extent
%
%	@param	High	high extent

%according to Roig-Francoli
%voice(soprano,[[c,''],3],[[g,''],4]).
%voice(alto,[[g,''],2],[[d,''],4]). 
%voice(tenor,[[c,''],2],[[g,''],3]).
%voice(bass,[[e,''],1],[[c,''],3]). 

%according to aldwell schachter
voice(soprano,[[c,''],3],[[c,''],5]).
voice(alto,[[g,''],2],[[c,''],4]).
voice(tenor,[[c,''],2],[[g,''],3]).
voice(bass,[[e,''],1],[[g,''],2]).

%%	refNoteNum(?Note:note, ?MidiNum:integer) is det.
%
%	definition of relationship between notes and midi note numbers.  establishes middle c = =|[[c,''],3]|=.
%
%	hardcoded as =|refNoteNum([[c,''],3],60).|=
%
%	notes are =|[Chroma,Octave:integer]|=  (octave >= 1 -- i forget why, may no longer be required?)
%
%	chromas are =|[WhiteKeyName,Accidental]|=
%
%	@param	Note		the reference note
%
%	@param	MidiNum		the corresponding midi note number

refNoteNum([[c,''],3],60).

%%	consonant(?Interval) is multi.
%
%	definition of consonant intervals
%
%	hardcoded so quality must be in =|[perfect,major,minor]|= (notice these are closed under inversion) and degree must be in =|[octave,third,fourth,fifth,sixth]|=
%
%   this isn't a subjective call, it is determined by how many overtone harmonics are shared between tones, and is an axiom of common practice harmony.  chords may not double any dissonance over the root or bass.  
%   dissonances (non-consonances) must resolve by step on their next movement (or exchange with a voice that does).  theory: this is enough asymmetric motive force to drive progressive movement with no functional interpretation/restrictions, allowing maximum freedom in tonality ("borrowed" and "passing" chords, "modulation" and "tonicization", may all be ad hoc patches on an overly-dogmatic theory of tonal function focused on repose at a single tonic).  i would like to contrast a concern for local resolution with schenker's focus on global key centers).
%   
%   ex: 
%   
%   ==
%   consonant([major,third]).
%   ==
%
%	@param	Interval		the interval as 2-element list [quality,degree]

consonant(Interval):-
	Interval=[Quality,Degree],
	member(Quality,[perfect,major,minor]),
	member(Degree,[octave,third,fourth,fifth,sixth]).

%%	functions(?F:list) is det.
%
%	definitions for functions served by chords with roots relative to a tonic.
%
%	hardcoded to =|[tonic,supertonic,mediant,subdominant,dominant,submediant,leadingTone]|=
%
%	need to change to be =|[tonic,'b supertonic',supertonic,'b mediant',mediant,subdominant,'b dominant',dominant,'b submediant',submediant,subtonic,leadingTone]|=, which will require changing the function interpretation predicates
%
%	@param	F		the ordered list of function names -- currently the same length as whiteKeys, but after fix, will be same length as octave

functions([tonic,supertonic,mediant,subdominant,dominant,submediant,leadingTone]).
%functions([tonic,'b supertonic',supertonic,'b mediant',mediant,subdominant,'b dominant',dominant,'b submediant',submediant,subtonic,leadingTone]). % need to add consistency check to verify same length as octave

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% NO HARDCODED FACTS BELOW THIS POINT
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% public predicates related to chromas, notes, and intervals

%%	raiseNumHalfSteps(?Note1, ?Note2, ?NumHalfSteps) is multi.
%
%	find number of half steps between two notes
%
%	notice the octave breaks convention in octaveBreaks/1
%
%	ex:
%	==
%	raiseNumHalfSteps([[g, x], 2],[[c,''],4],15).
%	==
%
%	@param	Note1			Note in format [[Chroma],OctaveNumber] ala note/1
%
%	@param	Note2			Note in format [[Chroma],OctaveNumber] ala note/1
%
%	@param	NumHalfSteps	NumHalfSteps from Note1 up to Note2

raiseNumHalfSteps(A,B,H):- %can't compare H mod N to interval(A,B,_,V) cuz some A,B are not intervals (c to cbb is beyond a diminished octave)...  plus it would infinite loop with the check in interval/4
	octaveBreaks(W),
	note(A),
	note(B),
	A=[[C1,A1],O1],
	B=[[C2,A2],O2],
	whiteKey(C1,N1),
	whiteKey(C2,N2),
	whiteKey(W,NR),
	D1 is N1-NR,
	D2 is N2-NR,
	accidental(A1,M1),
	accidental(A2,M2),
	K is D2+M2-M1-D1,
	numNotesPerOctave(N),
	(
		( ((D1>=0, D2>=0);(D1<0, D2<0)), H is (K+N*(O2-O1)) )
		;
		(D1>=0, D2<0, H is (K+N*(O2-O1+1)))
		;
		(D1<0, D2>=0, H is (K+N*(O2-O1-1)))
	).

/* known to work correctly.  above is potentially incorrect simplification. */
/*
raiseNumHalfSteps(A,B,H):- 
	octaveBreaks(OBW),
	note(A),
	note(B),
	A=[[C1,A1],O1],
	B=[[C2,A2],O2],
	whiteKey(C1,N1),
	whiteKey(C2,N2),
	whiteKey(OBW,NR),
	D1 is N1-NR,
	D2 is N2-NR,
	accidental(A1,M1),
	accidental(A2,M2),
	accidental('',MR),
	E1 is M1-MR,
	E2 is M2-MR,
	T1 is D1+E1,
	T2 is D2+E2,
	numNotesPerOctave(N),
	(
		( ((D1>=0, D2>=0);(D1<0, D2<0)), H is ((T2-T1)+N*(O2-O1)) )
		;
		(D1>=0, D2<0, H is ((T2-T1)+N*(O2-O1+1)))
		;
		(D1<0, D2>=0, H is ((T2-T1)+N*(O2-O1-1)))
	).
*/

%%	notes_chromas_nums(?Notes, ?Chromas, ?Nums) is multi.
%
%	finds the Chromas and MIDI note numbers of a list of Notes
%
%	Notes, Chromas, and Nums are all lists that are the same length, and contain information about corresponding Notes
%
%	ex:
%	==
%	notes_chromas_nums([[[e, b], 3],[[e, b], 4],[[e, b], 5],[[e, ''], 1]], [[e, b], [e, b], [e, b], [e, '']],[63,75,87,40]).
%	==
%
%	@param	Notes			list of Notes in format [[Chroma],OctaveNumber] ala note/1
%
%	@param	Chromas			list of Chromas in format [WhiteKey,Accidental] ala chroma/1
%
%	@param	Nums			list of corresponding MIDI note numbers

notes_chromas_nums([],[],[]).
notes_chromas_nums([N|Ns],[C|Cs],[M|Ms]):-
	noteNum(N,M),
	N=[C,_],
	notes_chromas_nums(Ns,Cs,Ms).

%%	all_enharmonics(+Chromas, -EnharmonicChromas) is det.
%
%	finds all chromas enharmonic to Chromas in a list
%
%	ex:
%	==
%	all_enharmonics([[c,'']],[[b, #], [c, ''], [d, bb]].).
%	all_enharmonics([[c,''],[e,'']],[[b, #], [c, ''], [d, bb], [d, x], [e, ''], [f, b]]).
%	==
%
%	@param	Chromas						list of Chromas in format [WhiteKey,Accidental] ala chroma/1 
%
%	@param	EnharmonicChromas			sorted list of all Chromas in format [WhiteKey,Accidental] ala chroma/1 enharmonic to at least one member of Chromas

all_enharmonics(B,L):-
	setof(X,M^(member(M,B),enharmonic(M,X)),L1),
	sort(L1,L).

%%	enharmonic(?Chroma1, ?Chroma2) is multi.
%
%	determine if two Chroma are enharmonic (members of the same pitch class)
%
%	ex:
%	==
%	enharmonic([b,x],[c, #]).
%	==
%
%	@param	Chroma1				Chroma in format [WhiteKey,Accidental] ala chroma/1 (if only one argument is ground, faster to instantiate this one)
%
%	@param	Chroma2				Chroma in format [WhiteKey,Accidental] ala chroma/1 

enharmonic(A,B):-
	allSpellingsOfChroma(A,L),
	member(B,L).

%%	enharmonicLists(+ChromaList1, +ChromaList2) is semidet.
%
%	determine if two Chroma lists contain any Chroma not enharmonic to the other
%
%	ex:
%	==
%	enharmonicLists([[b,x],[e,''],[f,x]],[[g,''],[e,''],[c, #],[d,b],[f,b]]).
%	==
%
%	@param	ChromaList1				list of Chroma in format [WhiteKey,Accidental] ala chroma/1
%
%	@param	ChromaList2				list of Chroma in format [WhiteKey,Accidental] ala chroma/1 

enharmonicLists(A,B):-
	all_enharmonics(A,L1),
	all_enharmonics(B,L2),
	L1 = L2.

%%	intervals(?Root, ?ChromaList, ?IntervalList) is multi.
%
%	find all intervals of a list of chromas over a root
%
%	at least one of ChromaList or IntervalList has to be ground, or else we get stuck making infinite lists.  could be fixed.
%
%	ex:
%	==
%	intervals([c,''],[[b,x],[e,''],[g,b]],L). % fails - there is no interval from [c,''] up to [b,x] -- a double augmented seventh
%	intervals([c,''],[[b,#],[e,''],[g,b]],[[augmented, seventh], [major, third], [diminished, fifth]]).
%	==
%
%	@param	Root				list of Chroma in format [WhiteKey,Accidental] ala chroma/1
%
%	@param	ChromaList			list of Chroma in format [WhiteKey,Accidental] ala chroma/1 
%
%	@param	IntervalList		list of Intervals in format [Quality,Degree] ala interval/1 

intervals(Root,ChromaList,IntervalList):-
	chroma(Root),
	%any cleaner way to design this?
	( 
	 (\+ground(IntervalList),\+ground(ChromaList),print_message(error,'whats this'),throw('at least one of ChromaList or IntervalList has to be ground'));
	 (ground(IntervalList),length(IntervalList,N),length(ChromaList,N));
	 (ground(ChromaList),length(ChromaList,N),length(IntervalList,N))
	),
	duplicate(Root,N,Roots),
	maplist(interval,Roots,ChromaList,IntervalList).

%%	intervals(+ChromaList, -IntervalList) is det.
%
%	find all ascending intervals moving from left to right in a chroma list
%
%	note non-existant intervals like double-augmented-fifths (eg c to gx) are omitted.
%
%	ex:
%	==
%	intervals([[c,''],[e,''],[g,x],[a,b]],[1-2-[major, third], 1-4-[minor, sixth], 2-3-[augmented, third], 2-4-[diminished, fourth]]).
%	==
%
%	@param	ChromaList			list of Chroma in format [WhiteKey,Accidental] ala chroma/1 
%
%	@param	IntervalList		list of Intervals in format N-M-[Quality,Degree] ala interval/1, where N and M are the 1-based indices of the corresponding Chroma in ChromaList 

intervals(Chromas,Ints):-
	findall(N-M-Int,
			(nth1(N,Chromas,C1),
			 nth1(M,Chromas,C2),
			 M > N,
			 interval(C1,C2,Int)),
			Ints).

%%	intervalSort(+IntervalList, ?Sorted) is det.
%
%	sort intervals according to their size in half steps, and then by degree distance
%
%	sorted according to the 2-tuple second arg of IntervalList/2
%
%	ex:
%	==
%	intervalSort([[minor,third],[major,second],[augmented,second]],[[major, second], [augmented, second], [minor, third]]).
%	==
%
%	@param	IntervalList	list of Intervals in format [Quality,Degree] ala interval/1
%
%	@param	Sorted			list of Intervals in format [Quality,Degree] ala interval/1 sorted according to their size in half steps, and then by degree distance

intervalSort(IntervalList,SortedIntervalList):-
	maplist(intervalSize,IntervalList,IntervalSizes),
	pairs_keys_values(KeyedIntervalList,IntervalSizes,IntervalList),
	keysort(KeyedIntervalList,SortedKeyedIntervalList),
	pairs_values(SortedKeyedIntervalList,SortedIntervalList).
	
%%	intervalInversion(?Interval, ?Inverted) is multi.
%
%	find inteval inversions
%
%	ex:
%	==
%	intervalInversion([diminished, third],[augmented, sixth]).
%	==
%
%	@param	Interval		Interval in format [Quality,Degree] ala interval/1
%
%	@param	Inverted		Inversion of Interval in format Interval in format [Quality,Degree] ala interval/1
	
intervalInversion(A,B):-
	interval(A),
	once((interval(C1,C2,A), %once to avoid finding lots of chroma exemplars, but need to wrap the conjuction because even if two chromas make an interval, their inversion doesn't necessarily exist.  ex: can have gx min 2 (a#), but not gx maj7 (fx#)
		  interval(C2,C1,B))).	

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% public predicates related to scales, modes, keys, and degrees

%%	scale(?L) is multi.
%
%	find all possible scales
%
%	the most general possible definition of a scale is a sequence of strictly positive intervals whose halfstep sizes add up to the total number of halfsteps per octave.  that is it must be strictly monotonic without enharmonics or overlaps (if have [d,b], cannot have either [c,#] or [c,x]).
%
%	ex:
%	==
%	scale([1, 1, 1, 1, 1, 1, 6]).
%	==
%
%	@param	L		a sequence of integers >=1 that add up to N, where N is numNotesPerOctave(N)

scale(L):-
	numNotesPerOctave(N),
	scale_help(L,N).

%%	scale(?Root, ?Scale, ?Chromas) is multi.
%
%	find Chromas of a Scale over a Root Chroma
%
%	the Chroma names are only determined for scales of the same length as whiteKeys/1, so we fail otherwise.
%
%	note that not all sequences of 7 positive integers that add to 12 can be a scale over every Root Chroma -- there is no gx major scale because there is no Chroma fx#
%
%	ex:
%	==
%	scale([e,b],[1, 1, 2, 4, 1, 1, 2],[[e, b], [f, b], [g, bb], [a, bb], [b, ''], [c, ''], [d, b]]).
%	scale([g,x],[2,2,1,2,2,2,1],C). % fails -- no such chroma as fx#
%	==
%
%	@param	Root		a Chroma of the form [WhiteKey,Accidental] ala chroma/1
%
%	@param	Scale		a sequence of integers >=1 that add up to N, where N is numNotesPerOctave(N), ala scale/1
%
%	@param	Chromas		the corresponding sequence of Chromas with the specified intervals over Root (each white key, inflected by some accidental, will be represented exactly once)

%note isn't guarnteed to exist - there is no  Gx major scale -- it would need Fx#
scale(C,S,Cs):-
	whiteKeys(L),
	length(L,N), %scale spelling is only determined if it is the same length as our white keys
	chroma(C),
	scale(S),
	length(S,N),
	[B,_]=C,
	whiteKeyList(B,WS,_),
	scale_help(C,WS,S,Cs,0).	

/* define as in module comments/wikipedia
properScale(
primeScale(
*/

%%	mode(?Name) is multi.
%
%	find mode names
%
%	ex:
%	==
%	mode(phrygian).
%	==
%
%	@param	Name		any of the mode names defined by the third argument of modeName/3

mode(M):-
	mode(M,_).
	
%%	mode(?Name, ?Scale) is multi.
%
%	find mode names and their scales
%
%	ex:
%	==
%	mode(phrygian,[1, 2, 2, 2, 1, 2, 2]).
%	==
%
%	@param	Name		any of the mode names defined by the third argument of modeName/3
%	
%	@param	Scale		the corresponding Scale ala scale/1
	
mode(M,S):-
	modeName(N,L,X),
	((atom(X),X=M) ; member(M,X)),
	mode_help(N,L,S).

mode_help(N,S,L):-
	scale(S),
	length(S,M),
	numlist(1,M,Ns),
	member(N,Ns),
	Q is N-1,
	shiftList(Q,S,L).

%%	mode(?Root, ?Name, ?Chromas) is multi.
%
%	find Chromas for Named modes starting on Root
%
%	ex:
%	==
%	mode([g,x],phrygian,[[g, x], [a, #], [b, #], [c, x], [d, x], [e, #], [f, x]]).
%	mode([g,x],major,L). % fails - no such chroma as fx#
%	==
%
%	@param	Root		a Chroma ala chroma/1
%	
%	@param	Name		any of the mode names defined by the third argument of modeName/3
%
%	@param	Chromas		the corresponding Chromas ala chroma/1

mode(Root,Name,Chromas):-
	mode(Name,S),
	scale(Root,S,Chromas).

%%	degree(?N, ?Key, ?Chroma) is multi.
%
%	find scale Degrees in Keys
%
%	ex:
%	==
%	degree(4,[[g,''],dorian],[c,'']).
%	==
%
%	@param	N			the 1-based degree (1 is Root) -- can be any integer >=1 (should fix to allow negatives)
%	
%	@param	Key			Key in format [Chroma,Mode] ala key/1
%
%	@param	Chroma		the Chroma at the specified Degree in the Key, ala chroma/1

degree(N,Key,Chroma):-
	Key=[Root,Mode],
	mode(Mode,S),
	scale(Root,S,Cs),
	M is N-1,
	shiftList(M,Cs,[Chroma|_]).

%%	key(?Key) is multi.
%
%	keys specify a root Chroma in a Mode
%
%	ex:
%	==
%	key([[a, b], locrianRaise2]).
%	==
%
%	@param	Key			Key in format [Chroma,Mode], ala chroma/1 and mode/1

key([C,M]):-
	chroma(C),
	mode(M).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% public predicates related to chords, voices, and voice leading

%%	chord(?Chromas, ?Root, ?Quality, ?Size) is multi.
%
%	chord recognition - find Chromas associated with a chord with Root, Quality, and Size
%
%	there are 23,380 of these, but only 10,084 are unique cuz our generator method makes a few dupes (expect fewer when we eliminate R333, RR33, and R337)
%
%	the Chromas-chord relationship is never ambiguous when the constraints in chordChromaClasses/4 are followed
%
%	==
%	chord(A,[f,#],halfdiminished,seven).
%	
%	A = [[a, ''], [c, ''], [e, ''], [f, #]] ;
%	A = [[c, ''], [a, ''], [e, ''], [f, #]] ;
%	A = [[c, ''], [e, ''], [a, ''], [f, #]] ;
%	A = [[c, ''], [e, ''], [f, #], [a, '']] ;
%	A = [[a, ''], [e, ''], [c, ''], [f, #]] ;
%	A = [[e, ''], [a, ''], [c, ''], [f, #]] ;
%	A = [[e, ''], [c, ''], [a, ''], [f, #]] ;
%	A = [[e, ''], [c, ''], [f, #], [a, '']] ;
%	A = [[a, ''], [e, ''], [f, #], [c, '']] ;
%	A = [[e, ''], [a, ''], [f, #], [c, '']] ;
%	A = [[e, ''], [f, #], [a, ''], [c, '']] ;
%	A = [[e, ''], [f, #], [c, ''], [a, '']] ;
%	A = [[a, ''], [c, ''], [f, #], [e, '']] ;
%	A = [[c, ''], [a, ''], [f, #], [e, '']] ;
%	A = [[c, ''], [f, #], [a, ''], [e, '']] ;
%	A = [[c, ''], [f, #], [e, ''], [a, '']] ;
%	A = [[a, ''], [f, #], [c, ''], [e, '']] ;
%	A = [[f, #], [a, ''], [c, ''], [e, '']] ;
%	A = [[f, #], [c, ''], [a, ''], [e, '']] ;
%	A = [[f, #], [c, ''], [e, ''], [a, '']] ;
%	A = [[a, ''], [f, #], [e, ''], [c, '']] ;
%	A = [[f, #], [a, ''], [e, ''], [c, '']] ;
%	A = [[f, #], [e, ''], [a, ''], [c, '']] ;
%	A = [[f, #], [e, ''], [c, ''], [a, '']] ;
%	false.
%	==
%
%	@param	Chromas		any permutation of a list of chromas with length specified by numVoices/1 that satisfies chordChromaClasses/4 for this (Chromas, Root, Quality, Size)
%
%	@param	Root		a chroma ala chroma/1
%
%	@param	Quality		chord quality ala second argument of chordQuality/3
%
%	@param	Size		chord size ala third argument of chordQuality/3

chord(Chromas,Root,Quality,Size):-
	chordChromaClasses(Classes,Root,Quality,Size),
	numVoices(Len),	
	generate(Cs,Classes,Len),
	permutation(Cs,Chromas).

%%	numVoices(?N) is det.
%
%	find the number of voices defined by voice/3
%
%	ex:
%	==
%	numVoices(4).
%	==
%
%	@param	N			the number of voices defined by voice/3

numVoices(N):-
	aggregate_all(count,Name,voice(Name,_,_),N).

%%	inRange(?NoteNums) is multi.
%
%	find list of NoteNums in ranges specified by voice/3
%
%	inRange/1 enforces the voice location rules specified in selectVoicing/1
%
%	* one note for each voice defined by voice/3, in the specified range, without overlap (unison OK.  aldwell schacter and roig-francoli both suggest that brief crossing can be acceptable in inner voices, we may relax this constraint.).
%	* at most one octave between adjacent voices (2 octaves may separate the bottom 2 voices)
%
%	ex:
%	==
%	inRange([40, 48, 55, 61]).
%	==
%
%	@param	NoteNums		list of MIDI NoteNums in ranges specified by voice/3

inRange(Nums):-
	ranges(Ranges),
	maplist(between,Ranges,Nums),
	numNotesPerOctave(N),
	%msort(Nums,Nums),
	diff(Nums,[BassInt|Diffs]),
	maplist(=<(0),[BassInt|Diffs]), %could relax this - brief voice crossings in the inner voices are apparently ok -- p.145 roig-francoli
	max_list(Diffs,Biggest),
	Biggest =< N,
	BassInt =< 2*N.
	
%%	consonantOver(?Chroma1, ?Chroma2) is multi.
%
%	find consonant Chroma relationships
%
%	consonance defined by consonant/1 (note this is a symmetric relation - inversions of consonances are always consonant (may not hold outside of 12TET?))
%
%	ex:
%	==
%	consonantOver([c,''],[e,'']).
%	==
%
%	@param	Chroma1			a chroma, ala chroma/1
%
%	@param	Chroma2			a chroma, ala chroma/1
	
consonantOver(Chroma1,Chroma2):-
	interval(Chroma1,Chroma2,Interval),
	consonant(Interval).

%%	voiceLeadingOK(?VoicingList) is multi.
%
%	find sequences of Voicings that comply to voice leading rules
%
%	each member of VoiceingList is a 2-list [NoteNumList,Voicing], where Voicing is as selectVoicing/1, and NoteNumList is the corresponding list of note numbers ala noteNum/2
%
%	in addition to enforcing that each Voicing adhere to the Voicing rules in selectVoicing/1, voiceLeadingOK/1 enforces: 
%	
%		* no dissonant melodic intervals within a voice
%		* at most one voice leaps more than a third on any transition
%		* that the next movement in a voice after a leap greater than a third is stepwise in the opposite direction
%		* at least one voice moves during each transition
%		* no parallel or contrary perfect fifths or perfect octaves (incl unisons)
%		* no direct/hidden perfect fifths or perfect octaves (landing on a perfect fifth via similar motion) between the outer voices unless the top voice moves by step
%		* no consecutive fifths of any quality btw outer voices (unless neither moved)
%		* no voice overlap (one voice crossing over an adjacent voice's previous position)
%
%	ex:
%	==
%	length(L,4),voiceLeadingOK(L),maplist(writeln,L).
%	[[55, 64, 67, 67], [[[[a, bb], 2], [[f, b], 3], [[a, bb], 3], [[a, bb], 3]], [f, b], minor, triad, first]]
%	[[47, 55, 65, 74], [[[[c, b], 2], [[a, bb], 2], [[g, bb], 3], [[e, bb], 4]], [a, bb], dominant, seven, first]]
%	[[48, 57, 69, 72], [[[[d, bb], 2], [[b, bb], 2], [[b, bb], 3], [[d, bb], 4]], [b, bb], minor, triad, first]]
%	[[48, 60, 70, 76], [[[[d, bb], 2], [[d, bb], 3], [[c, bb], 4], [[f, b], 4]], [d, bb], dominant, seven, root]]
%	==
%
%	@param	VoicingList			each member of VoiceingList is a 2-list [NoteNumList,Voicing], where Voicing is as selectVoicing/1, and NoteNumList is the corresponding list of note numbers ala noteNum/2
%
%	@bug seems to allow multiple voices to leap more than a third on individual transitions, i think because intervals/2 omits non-existant intervals
%
%	@tbd
%
%	* dissonance resolution rules
%	* can't double dissonance over tonic (at least leading tone), and they have to resolve a certain way (leading tone to tonic unless voice above goes to that note - roig-francoli p.144 says LT jumping up a 4th sometimes ok!?!)
%		* but need to know functional interpretation of chord to know what is tonic, and i'm trying to be agnostic about that
%		* put this in separate place using the key recommendation idea
%	* highest and lowest points in outer voices should occur once and at different times from each other

voiceLeadingOK(L):-
	blankState(B),
	length(L,N),
	length(M,N),
	voiceLeadingOK(M,B),
	maplist(addNoteNums,M,L). %cuz it seems pyswip only allows a single running query

voiceLeadingOK([A,B|C],InState):-
	voiceLeadingOK(A,B,InState,OutState),
	((C = [], blankState(OutState)); voiceLeadingOK([B|C],OutState)).

addNoteNums(V,O):-
	selectVoicing(V),
	V=[Ns,_,_,_,_],
	O=[Nums,V],
	maplist(noteNum,Ns,Nums).

blankState(L):-
	numVoices(V),
	length(L,V),
	maplist(=([]),L).

voiceLeadingOK(V1,V2,InState,OutState):-
	numVoices(V),
	selectVoicing(V1),
	writeln(['doing: ',V1]),
	selectVoicing(V2),
	write('.'),	
	V1\=V2,
	V1=[Notes1,_,_,_,_],
	V2=[Notes2,_,_,_,_],	
	maplist(jumpOK,Notes1,Notes2,State),
	include(functor(jump),State,Jumps),
	length(Jumps,NJ),
	NJ=<1, %only one jump, probably overly restrictive, prevents many voice exchanges -- could relax this in cases where chord root and quality don't change
	maplist(transformState,InState,State,OutState),
	notes_chromas_nums(Notes2,Chromas2,Nums2),
	notes_chromas_nums(Notes1,Chromas1,Nums1),
	intervals(Chromas2,Ints), %does voiceLeadingOK realize that intervals that don't exist won't be in this list?  like c to gx is a double augmented fifth that will just be omitted.
	findall(N-M-I,(member(N-M-[perfect,I],Ints),member(I,[octave,fifth])),P5sOs), %why don't P4's matter?
	forall(member(N-M-I,P5sOs),
		   (nth1(N,State,[S1]),
		    nth1(M,State,[S2]),
			(S1=..[_,Dir1];S1=none),
			(S2=..[Kind2,Dir2];S2=none),
			nth1(N,Chromas1,C1),
			nth1(M,Chromas1,C2),
			interval(C1,C2,[Qual,I2]),
			((I=I2,Qual=perfect) -> (S1 = none ; S2 = none) ; true), %no parallel or contrary consecutive P5/PO's.  i think oblique consecutive P5/PO's OK (one voice jumps by an octave)?  roig-francoli not clear p.143. 
			((N=1,M=V,S1\=none,S2\=none,Dir1=Dir2) -> Kind2=step ; true) %direct/hidden: no similar motion to P5/PO btw outer voices unless top voice moves by step.  do we need to worry about how a static note originally arrived?
		   )
		  ),
	nth1(1,Chromas2,B2),
	nth1(V,Chromas2,T2),
	nth1(1,State,SB),
	nth1(V,State,SV),
	(interval(B2,T2,[_,fifth])
	 ->
	  ((SB=none, SV=none)
	   ;
	   (nth1(1,Chromas1,B1),
	    nth1(V,Chromas2,V1),
	    \+interval(B1,V1,[_,fifth]) %no consecutive fifths of any kind btw outer voices unless neither moved
	   ) 
	  )
	 ; 
	 true
	),  
	noOverlap(Nums1,Nums2), %no voice crossing -- could relax this a little - roig-francoli p.145 says it's ok btw inner voices (tho unclearly seems to say crossing but not overlap OK?)  also, i did this based on pitch rather than note spelling -- as if fx>gb, but there's an outside chance that degree matters
	write(m).

functor(A,B):-
	functor(B,A,_).

noOverlap(Ns,Ms):-
	length(Ms,V),
	forall(nth1(K,Ms,M),
		   ((K is V;(KUp is K+1,nth1(KUp,Ns,N),M=<N)),
		    (K is 1;(KDown is K-1,nth1(KDown,Ns,N2),M>=N2)))).
	
jumpOK(Note1,Note2,State):-
	numNotesPerOctave(N),
	raiseNumHalfSteps(Note1,Note2,J),
	AbsJ is abs(J),
	AbsJ=<N,
	Note1=[C1,_],
	Note2=[C2,_],
	(J>=0 -> (Dir=ascending, interval(C1,C2,Int)); (Dir=descending, interval(C2,C1,Int))),
	(consonant(Int) ; (Int=[Qual,second],member(Qual,[major,minor]))), %is it more correct to exclude dim/aug 2?  note: roig-francoli claims dim 5, and in minor, dim 4 or dim 7 are possible in descending direction in the bass.  (p.142), then says diminished ok for everyone in all directions p. 148
	(AbsJ > 4 %jumps are anything bigger than a third (should aug third or dim fourth count?  my guess is that degree is irrelevant, only halfstep distance matters because this rule is about melodic shaping, not harmony (this is why we don't refer to intervalSize([major,third],(N,_))) -- but as these are dissonant, doesn't actually occur) 
	 -> Jump=jump(Dir)
	 ; (AbsJ = 0 %if dim second were consonant, should it count as no motion?  my guess: yes, as per above 
	    -> Jump=none
		; ( AbsJ =< 2 %if aug/dim octave, aug second, or dim third were consonant, would they count as a step?  my guess: yes except for aug second, as per above
			-> Jump=step(Dir) %jumps need to be opposed by halfstep/step on next motion
			; Jump=motion(Dir)
		  )
	   )
	),
	State=[Jump].

transformState([A],[none],[A]).
transformState([jump(ascending)],[step(descending)],[]).
transformState([jump(descending)],[step(ascending)],[]).
transformState([],[jump(A)],[jump(A)]):-
	!. %red cut so don't have to enumerate rules for none, step, and motion below.
transformState([],[_],[]).
	
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% public predicates for functional interpretation of chords in keys

%%	function(?QualifiedFunction) is multi.
%
%	defines the successive application of functions on a key in a mode
% 
%	QualifiedFunction is of the form [[RomanNumeralList],Key], where Key is of the form [Chroma,Mode].  
%	the RomanNumerals represent functions that are sequentially applied to the tonic Key.  
%
%	for example, V of c major is g dominant.  II of that V would be a minor.  
%
%	'I's are not used because they are tonic identities that would clutter our lists -- this should be reconsidered and would be alleviated by moving to bredth-first exploration.
%
%	ex:
%	==
%	function([['V'],        [[c,''],major]]). % V in c major is g dominant
%	function([['II','V'],   [[c,''],major]]). % II/V in c major is a minor
%
%	function([['I'],        [[c,''],major]]). % fails!  use the following instead:
%	function([[],           [[c,''],major]]). % tonic
%	==
%
%	@param	QualifiedFunction	a function in the form [[RomanNumeralList],Key], with Key of form [Chroma,Mode].  if RomanNumeralList is not ground, it is explored depth-first (an ever-extending list of 'II's), which should be changed to bredth-first.

function([F,K]):-
	functionList(F),
	key(K).

%%	function_roman(?Function, ?RomanNumeral) is multi.
%
%	associations between function names and roman numerals.
%
%	need to change to use new functions/1, which will mean that the roman numeral may be modified with a flat sign.
%
%	ex:
%	==
%	function_roman(subdominant,'IV').
%	==
%
%	@param	Function			the function name as defined by functions/1
%
%	@param	RomanNumeral		a capitalized roman numeral corresponding to the position of Function in functions/1

function_roman(F,R):-
	functions(L),  %need to fix to use new functions()
	nth1(N,L,F),
	romanNumeral(N,R).

%%	function(?Chord, ?QualifiedFunction) is multi.
%
%	relation between specific chords and the function they serve in specific keys/modes
%
%	Chord is in the form [ChromaSet, Root, Quality, Size], as in chordChromaSet/4 (note that ChromaSet does not contain dupes and is ordered by degree!).  Chord fills the QualifiedFunction specified. 
%
%	if multiple chords match, they will all have the same Root, but will vary in Size and therefore Quality (V of c major could be g major triad or g dominant seven).
%
%	QualifiedFunction is in the form [[RomanNumeralList],Key], where Key is of the form [Chroma,Mode], as in function/1.  
%
%	in sequential applications, the tonic mode is assumed constant ('major' in the example).
%
%	ex:
%	==
%	function( [[[c, ''], [e, ''], [g, ''], [b, '']],    [c, ''], major, seven],  [            [],[[c,''],major]]  ).
%	function( [[[g, ''], [b, ''], [d, '']],             [g, ''], major, triad],  [         ['V'],[[c,''],major]]  ).
%	function( [[[d, ''], [f, #], [a, '']],              [d, ''], major, triad],  [     ['V','V'],[[c,''],major]]  ).
%	function( [[[e, ''], [g, ''], [b, ''], [d, '']],    [e, ''], minor, seven],  [['II','V','V'],[[c,''],major]]  ).
%
%	% we currently disallow 'I' functions so we don't clutter our applied function list with identities.  see first example above and functor/1.
%	function( X,                                                                          [['I'],[[c,''],major]]  ).  %fails.  
%	==
%
%	@param	Chord				a chord in the form [ChromaSet, Root, Quality, Size], ala chordChromaSet/4
%
%	@param	QualifiedFunction	a function in the form [[RomanNumeralList],Key], with Key of form [Chroma,Mode], ala function/1

function(Chord,Function):-
	simplifyFunction(Function,Simplest),
	Simplest=[FunctionList,Key],
	((FunctionList=[],N=1);(FunctionList=[RN],romanNumeral(N,RN))),
	Cs=[0,2,4],
	(Ints=Cs;append(Cs,[6],Ints)),
	maplist(plus(N),Ints,Degrees),
	length(Degrees,Sz),
	duplicate(Key,Sz,Keys),
	maplist(degree,Degrees,Keys,ChromaList),
	chordChromaSet(ChromaList,Root,Quality,Size),
	Chord=[ChromaList,Root,Quality,Size].

%%	simplifyFunction(?QualifiedFunction, ?SimplestFunction) is multi.
%
%	eliminate (add) applied functions from (to) a qualified function 
%
%	QualifiedFunction is in the form [[RomanNumeralList],Key], where Key is of the form [Chroma,Mode], as in function/1.  
%
%	SimplestFunction is in the same form and is the simplest equivalent non-tonic function.  
%
%	for example, II/V/V/c major is e minor.  this is a complicated applied function.  the simplest function of e minor (aside from the trivial tonic of e minor) is the II of d major.
%
%	ex:
%	==
%	simplifyFunction([['II','V','V'],[[c,''],major]],[['II'], [[d, ''], major]]).
%	==
%
%	@param	QualifiedFunction	a function in the form [[RomanNumeralList],Key], with Key of form [Chroma,Mode], ala function/1
%
%	@param	SimplestFunction	a function in the same form, but with no application levels.  tonics are trivially excluded.

simplifyFunction(F,A):-
	function(F),
	F=[FList,OldKey],
	OldKey=[OldRoot,Mode],
	( (length(FList,N),
	   member(N,[0,1])
	  ) 
	 -> 
	  A=F 
	 ; 
	  (last(FList,RN),
	   append(Smaller,[RN],FList),
	   romanNumeral(M,RN),
	   degree(M,[OldRoot,Mode],NewRoot),
	   NewKey=[NewRoot,Mode],
	   simplifyFunction([Smaller,NewKey],A)
	  )
	).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% public utility predicates of general interest -- could doc and move to utility library

shiftList(0,L,L).
shiftList(N,[A|C],B):-
	N>0,
	M is N-1,
	append(C,[A],T),
	shiftList(M,T,B).

%%	cumsum(+List:numbers,?Sums:numbers) is det.
%
%	cumulative sum
%   
%   ex: 
%   
%   ==
%   cumsum([1, 2, 3],[1,3,6]).
%   ==
%
%	@param	List		List of numbers
%
%	@param	Sums		List of numbers that is the cumulative sum of List

cumsum(A,B):-
	cumsum(A,B,0).
cumsum([],[],_).
cumsum([A|As],[B|Bs],T):-
	B is T+A,
	cumsum(As,Bs,B).

duplicate(_,0,[]).
duplicate(X,N,[X|Xs]):-
	N>0,
	M is N-1,
	duplicate(X,M,Xs).
	
diff([A],[]):-
	number(A).
diff([A,B|C],[E|F]):-
	E is B-A,
	diff([B|C],F).

%there is surely a more efficient way to do this.  note the pattern in the sorted output of uniqueCombinations([a,b,c,d,e],4,X),maplist(writeln,X).
uniqueCombinations(List,Num,AllUniqueCombinations):-
	list_to_set(List,Set),
	duplicate(Set,Num,DupSet),
	findall(X,(length(X,Num),maplist(member,X,DupSet)),L),
	maplist(msort,L,Sorted),
	setof(X,member(X,Sorted),AllUniqueCombinations).

between([A, B],C):-
	between(A,B,C).	

/* did i mean for this to be different than subset?
drawnFrom(List,Source):-
	length(List,N),
	duplicate(Source,N,Reps),
	maplist(member,List,Reps).
*/

reduce([],[]).
reduce([M|Ms],[L|Ls]):-
	member(L,M),
	reduce(Ms,Ls).

% rand_insert(List1,List2,Output) - Output is List2 with the items in List1 inserted at random locations
rand_insert([],List,List).
rand_insert([A|As],List,Out):-
    length(List,N),
    M is N+1,
    P is random(M),
    insert(A,List,P,Next),
    rand_insert(As,Next,Out).

% insert(Item,List,Index,Output) - Output is List with Item inserted at Index (Index=0 means head, len(List,Index) means end)
insert(Item,A,0,[Item|A]).
insert(Item,[A|As],Pos,[A|C]):-
    Pos>0,
    NextPos is Pos-1,
    insert(Item,As,NextPos,C).   

% far faster than native is_set/1
fast_is_set(L):-
	length(L,N),
	sort(L,S),
	length(S,N).

% no one calls anymore -- why wasn't i using subset?
containsMembersOfSet(Set,L):-
	setof(Item,Set^(member(Item,L),member(Item,Set)),Matches),
	length(Matches,N),
	N>=1.

%%	conforms(+Sample, +ClassList) is det.
%
%	check conformation to a ClassList
%
%	class lists are lists of items each of the form functor(List), where functor is a requirement class (atLeast1, exactly1, or optional), and List is a list of items
%
%	ex:
%	==
%	conforms([a,d,f],  [atLeast1([b,a]),exactly1([g,h,f]),optional([q,e]),atLeast1([d])]).
%	conforms([a,d,f,w],[atLeast1([b,a]),exactly1([g,h,f]),optional([q,e]),atLeast1([d])]). % fails -- ClassList does not allow any 'w'
%	==
%
%	@param	Sample			a list of items that fit the constraints in ClassList
%
%	@param	ClassList		a list of items each of the form functor(List), where functor is a requirement class (atLeast1, exactly1, or optional), and List is a list of items
%
%	@bug  this does not enforce minimum requirements!  consider merging with generate/3 -- no one calls this anyway
%
%	==
%	theory:  ?- conforms([a,b,d,f,b],[atLeast1([b,a]),exactly1([g,h,f]),optional([q,e]),atLeast1([d])]).
%	true.
%	
%	theory:  ?- conforms([a,b,d,b],[atLeast1([b,a]),exactly1([g,h,f]),optional([q,e]),atLeast1([d])]).
%	true.
%	
%	theory:  ?- conforms([a,b,b],[atLeast1([b,a]),exactly1([g,h,f]),optional([q,e]),atLeast1([d])]).
%	true.
%	
%	theory:  ?- conforms([],[atLeast1([b,a]),exactly1([g,h,f]),optional([q,e]),atLeast1([d])]).
%	true.
%	==

/*works for chroma classes (chordChromaClasses/4) or num classes (chordNoeNums/2)*/
conforms(Nums,ClassList):-
	findall(Ns, 
		(member(X,ClassList),
		 X=..[Class,Ns],
		 findall(M,(member(M,Nums),member(M,Ns)),Matches),
		 length(Matches,N),
		 (Class=atLeast1 -> N>=1 ;
		  Class=exactly1 -> N=1 ;
		  Class=optional
		 )
		),
		NumLists),
	flatten(NumLists,NumList),
	subset(Nums,NumList).

%%	generate(-Sample, +ClassList, +Len) is multi.
%
%	generate all possible Samples that conform to a ClassList and Length
%
%	class lists are lists of items each of the form functor(List), where functor is a requirement class (atLeast1, exactly1, or optional), and List is a list of items
%
%	does not make every permutation (would need to in order to replace conforms/2) -- it does repeat some due to optional items after the minimum requirements have been satisfied.
%
%	ex:
%	==
%	theory:  ?- generate(X,[atLeast1([b,a]),exactly1([g,h,f]),optional([q,e]),atLeast1([d])],4).
%	X = [b, b, d, g] ;
%	X = [a, b, d, g] ;
%	X = [b, d, g, q] ;
%	X = [b, d, e, g] ;
%	X = [b, d, d, g] ;
%	X = [b, b, d, h] ;
%	X = [a, b, d, h] ;
%	X = [b, d, h, q] ;
%	X = [b, d, e, h] ;
%	X = [b, d, d, h] ;
%	X = [b, b, d, f] ;
%	X = [a, b, d, f] ;
%	X = [b, d, f, q] ;
%	X = [b, d, e, f] ;
%	X = [b, d, d, f] ;
%	X = [a, b, d, g] ;
%	X = [a, a, d, g] ;
%	X = [a, d, g, q] ;
%	X = [a, d, e, g] ;
%	X = [a, d, d, g] ;
%	X = [a, b, d, h] ;
%	X = [a, a, d, h] ;
%	X = [a, d, h, q] ;
%	X = [a, d, e, h] ;
%	X = [a, d, d, h] ;
%	X = [a, b, d, f] ;
%	X = [a, a, d, f] ;
%	X = [a, d, f, q] ;
%	X = [a, d, e, f] ;
%	X = [a, d, d, f] ;
%	false.
%	==
%
%	@param	Sample			a list of items that fit the constraints in ClassList
%
%	@param	ClassList		a list of items each of the form functor(List), where functor is a requirement class (atLeast1, exactly1, or optional), and List is a list of items
%
%	@param	Len				length of Sample

/*works for chroma classes (chordChromaClasses/4) or num classes (chordNoeNums/2)*/
generate(Nums,ClassList,Len):-
	findall(Ns,
		(member(X,ClassList),
		 X=..[Class,Ns],
		 member(Class,[atLeast1,exactly1])
		),
		OneEach),
	findall(Ns,
		(member(X,ClassList),
		 X=..[Class,Ns],
		 member(Class,[atLeast1,optional])
		),
		Optionals),
	append(Optionals,AllOptionals),
	length(OneEach,Req),
	R is Len-Req,
	duplicate(AllOptionals,R,DuplicatedOptionals),
	append(OneEach,DuplicatedOptionals,Master),
	reduce(Master,UnsortedNums),
	msort(UnsortedNums,Nums).
	
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% private domain specific helpers (should move these up next to the predicates they serve)
% in many cases a lambda closure would be very nice to associate these anonymously with the public predicate they serve.  apparently other prologs have them (visual prolog, lambda prolog, allegro proglog, mercury, also check out http://ha4.fajno.net/2007/12/14/simple-lambda-calculus-in-prolog/)
% =.., forall, maplist, and call/2 probably suffice...

% would be nice if this did a breadth instead of depth first walk
functionList([]).
functionList([F|Fs]):-
	function_roman(_,F),
	romanNumeral(N,F),
	N \= 1,
	functionList(Fs).

fixVal(A,B,C):-
	T is B-A,
	numNotesPerOctave(N),
	C is T mod N.	
	
whiteKeyList(First,Ws,FKs):-
	whiteKeys(L),
	keysort(L,SL),
	pairs_keys_values(SL,K,W),
	nth0(M,W,First),
	shiftList(M,W,Ws),
	shiftList(M,K,Ks),
	nth0(0,Ks,KR),
	maplist(fixVal(KR),Ks,FKs).

interval_help(Q,S,N,E):-
	intervalQuality(Q,E,T),
	intervalNamesTypes(M),
	nth0(N,M,[S,T]).

whiteKeyOK(N):-
	integer(N),
	N>=0,
	numNotesPerOctave(NR),
	NR>N.
	
scale_help(_,[],[],[],_).
scale_help(C,[W|Ws],[S|Ss],[[W,A]|Cs],N):-
	interval(C,[W,A],_,N),
	Q is N+S,
	scale_help(C,Ws,Ss,Cs,Q).
	
scale_help([],0).
scale_help([A|B],N):-
	N>0,
	numlist(1,N,L),
	member(A,L),
	M is N-A,
	scale_help(B,M).	

natch(X):-
	chroma(X),
	nth0(1,X,'').

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% public dynamically cached-out predicates
%
% fully enumerate the universe of whiteKeys, accidentals, chromas, notes, intervals, and chord specs for fast lookup
%
% one can get comprehensive lists of these ground facts using listing/1, ala listing(interval/4).
%
% the order of these predicates is fragile because of dependencies on the interleaved assert commands
% they are placed below the above definitions because the directives make use of them.
%
% is there a more paradigmatic way to do this?
% jan says: 
% term_expansion(blah(x,x), Clauses) :-
%       make_blah_clauses(Clauses).
%
% blah(x,x).

%%	accidental(?Name, ?HalfStepsUpFromNatural) is multi.
%
%	find accidental inflections
%
%	ex:
%	==
%	accidental(bb, -2).
%	accidental(b, -1).
%	accidental('', 0).
%	accidental(#, 1).
%	accidental(x, 2).
%	==
%
%	@param	Name					one of the accidentals in accidentals/1
%
%	@param	HalfStepsUpFromNatural	the corresponding number of half steps up from natural

slowAccidental(X,N):-
	accidentals(L),
	member(X,L),
	nth0(NR,L,''),
	nth0(RN,L,X),
	N is RN-NR.

:- abolish(accidental/2),forall(slowAccidental(X,N),assert(accidental(X,N))),compile_predicates([accidental/2]). %cache this heavily used static info

%%	whiteKey(?Name, ?HalfStepsUpFromFirstWhiteKey) is multi.
%
%	find white key positions
%
%	ex:
%	==
%	whiteKey(d,5).
%	==
%
%	@param	Name							one of the whiteKeys in whiteKeys/1
%
%	@param	HalfStepsUpFromFirstWhiteKey	the corresponding number of half steps up from the first whiteKey in whiteKeys/1

slowWhiteKey(A,B):-
	whiteKeys(L),
	member(B-A,L).
	
:- abolish(whiteKey/2),forall(slowWhiteKey(A,B),assert(whiteKey(A,B))),compile_predicates([whiteKey/2]). %cache this heavily used static info

%%	chroma(?Chroma) is multi.
%
%	chromas are of the form [WhiteKey,Accidental]
%
%	ex:
%	==
%	chroma([a,#]). 
%	chroma([b,'']). % natural
%	==
%
%	@param	Chroma		Chroma in the form [WhiteKey,Accidental], where WhiteKey is ala whiteKey/2 and Accidental is ala accidentals/1

slowChroma([W,A]):-
	whiteKey(W,_),
	accidental(A,_).

:- abolish(chroma/1),forall(slowChroma(A),assert(chroma(A))),compile_predicates([chroma/1]). %cache this heavily used static info

%%	octaveRange(?Lo, ?Hi) is det.
%
%	the octave range of the defined voices
%
%	the defined voice ranges limit what we consider acceptable octave numbers, and therefore keeps the domain of notes finite.
%
%	ex:
%	==
%	octaveRange(1,5).
%	==
%
%	@param	Lo		lowest octave num defined by a voice
%
%	@param	Hi		highest octave num defined by a voice

slowOctaveRange(LO,HO):-
	findall(L,voice(_,[_,L],_),Ls),
	findall(H,voice(_,_,[_,H]),Hs),
	sort(Ls,[LO|_]),
	sort(Hs,SHs),
	last(SHs,HO).

:- abolish(octaveRange/2),slowOctaveRange(A,B),assert(octaveRange(A,B)),compile_predicates([octaveRange/2]). %cache this heavily used static info

%%	note(?Note) is multi.
%
%	notes have the form [[Chroma],OctaveNumber] 
%
%	notice the octave breaks convention in octaveBreaks/1
%
%	ex:
%	==
%	note([[b, bb], 4]). % b-double-flat
%	==
%
%	@param	Note		a note of the form [[Chroma],OctaveNumber], where Chroma is ala chroma/1 and OctaveNumber is within OctaveRange/2

slowNote([C,O]):-
	chroma(C),
	octaveRange(LO,HO),
	numlist(LO,HO,L),
	member(O,L).

:- abolish(note/1),forall(slowNote(A),assert(note(A))),compile_predicates([note/1]). %cache this heavily used static info

%%	noteNum(?Note, ?N) is multi.
%
%	find the MIDI note number of a note
%
%	the range of noteNums is limited by the octave range of the voices defined by voice/3
%
%	notice the octave breaks convention in octaveBreaks/1
%
%	ex:
%	==
%	noteNum([[a, bb], 1],43).
%	==
%
%	@param	Note		a note of the form [[Chroma],OctaveNumber] ala note/1
%
%	@param	N			the MIDI note numer

slowNoteNum(A,N):-
	refNoteNum(RA,RN),
	raiseNumHalfSteps(RA,A,M),
	N is M+RN.

:- abolish(noteNum/2),forall(slowNoteNum(A,B),assert(noteNum(A,B))),compile_predicates([noteNum/2]). % cache this heavily used static info

%%	intervalQuality(?Quality, ?N, ?Type) is multi.
%
%	defines how intervals of perfect and imperfect types are modified by qualities
%
%	ex:
%	==
%	intervalQuality(augmented, 1, imperfect).
%	==
%
%	@param	Quality		as in intervalQualities/2
%
%	@param	N			the number of half steps away from perfect or major
%
%	@param	Type		as in intervalNamesTypes/1

slowIntervalQuality(Q,N,T):-
	intervalQualities(T,L),
	((T=perfect,nth0(RN,L,perfect)) ; (T=imperfect,nth0(RN,L,major))),
	nth0(NR,L,Q),
	N is NR-RN.
	
:- abolish(intervalQuality/3),forall(slowIntervalQuality(Q,N,T),assert(intervalQuality(Q,N,T))),compile_predicates([intervalQuality/3]). %cache this heavily used static info

%%	interval(?Interval) is multi.
%
%	Intervals are [Quality,Degree]
%
%	ex:
%	==
%	interval([major, third]).
%	==
%
%	@param	Interval		in the form [Quality,Degree], where Qualities and Degrees are as defined in intervalQualities/2 and intervalNamesTypes/1

slowInterval([Q,S]):-
	interval_help(Q,S,_,_).

:- abolish(interval/1),forall(slowInterval(A),assert(interval(A))),compile_predicates([interval/1]). %cache this heavily used static info		
	
%%	interval(?Chroma1, ?Chroma2, ?Interval, ?HalfSteps) is multi.
%
%	find the ascending Interval from Chroma1 UP to Chroma2 and the associated number of HalfSteps
%
%	ex:
%	==
%	interval([a, ''], [f, ''], [minor, sixth], 8).
%	interval([c,''],[b,x],A,B). % fails - there is no such thing as a double augmented seventh
%	==
%
%	@param	Chroma1			a chroma 
%
%	@param	Chroma2			a chroma 
%
%	@param	Interval		the interval from Chroma1 UP to Chroma2 in the form [Quality,Degree] ala interval/1
%
%	@param	HalfSteps		the associated number of HalfSteps 

slowInterval(A,B,I,N) :-	
	[C1,D1]=A,
	[C2,D2]=B,
	accidental(D1,M1),
	accidental(D2,M2),
	[Q,S]=I,
	whiteKeyList(C1,W,Ps),
	scaleName(G,major), %must not actually create this scale (as in scale(_,G,Cs)), it may not be defined
						%consider dim2 of Gx (Anat), this is a sensible interval but there is no  Gx major scale -- it would need Fx# -- can't have maj7 or aug7 of Gx at all
	append([0],G,GF),
	cumsum(GF,K),
	interval_help(Q,S,X,E),
	nth0(X,K,F),
	nth0(X,W,C2),
	nth0(X,Ps,P),
	numNotesPerOctave(L),
	N is (E+F) mod L,
	P is (N+M1-M2) mod L,
	assertion(raiseNumHalfSteps([A,_],[B,_],N)). %just to verify -- all should be ground by now...

:- abolish(interval/4),forall(slowInterval(A,B,I,N),assert(interval(A,B,I,N))),compile_predicates([interval/4]). %cache this heavily used static info

%%	interval(?Interval, ?HalfSteps) is multi.
%
%	find the number of HalfSteps associated with an Interval
%
%	ex:
%	==
%	Interval([minor, seventh], 10).
%	==
%
%	@param	Interval		an interval of the form [Quality,Degree] ala interval/1
%
%	@param	HalfSteps		the associated number of HalfSteps 

slowInterval(Interval,HalfSteps):-
	interval(Interval),
	once(interval(_,_,Interval,HalfSteps)).		
	
:- abolish(interval/2),forall(slowInterval(I,H),assert(interval(I,H))),compile_predicates([interval/2]). %cache this heavily used static info

%%	interval(?Chroma1, ?Chroma2, ?Interval) is multi.
%
%	find the ascending Interval from Chroma1 UP to Chroma2
%
%	ex:
%	==
%	interval([e, ''], [f, #], [major, second]).
%	interval([c,''],[b,x], X). % fails - there is no such thing as a double augmented seventh
%	==
%
%	@param	Chroma1			a chroma, ala chroma/1
%
%	@param	Chroma2			a chroma, ala chroma/1
%
%	@param	Interval		the interval from Chroma1 UP to Chroma2 in the form [Quality,Degree] ala interval/1
	
slowInterval(C1,C2,Interval):-
	interval(C1,C2,Interval,_).

:- abolish(interval/3),forall(slowInterval(C1,C2,I),assert(interval(C1,C2,I))),compile_predicates([interval/3]). %cache this heavily used static info
	
%%	intervalSize(?Interval, ?Spec) is multi.
%
%	determine degree and size of an interval
%
%	ex:
%	==
%	intervalSize([minor, third], (3, 2)).
%	==
%
%	@param	Interval		an interval of the form [Quality, Degree] ala interval/1
%
%	@param	Spec			a tuple of the form (HalfSteps,DegreeIndex), where HalfSteps describes Interval's size and DegreeIndex is the 0-based position of Degree in intervalNamesTypes/1
	
slowIntervalSize(Interval,(HalfSteps,Degree)):-
	interval(Interval,HalfSteps),
	[Q,S]=Interval,
	interval_help(Q,S,Degree,_).		

:- abolish(intervalSize/2),forall(slowIntervalSize(I,A),assert(intervalSize(I,A))),compile_predicates([intervalSize/2]). %cache this heavily used static info

%%	chordChromaClasses(-ClassList, ?Root, ?Quality, ?Size) is multi.
%
%	find chromaClasses for chord of Root, Quality, Size
%
%	chromaClasses enforce the doubling rules specified in selectVoicing/1
%
%	at least one root and one third, perfect fifths optional, exactly one seventh and altered fifth when present (dissonances over Root not doubled)
%
%	need to update to prevent R333, RR33, and R337, ie strictly more roots than anything else when fifth omitted (R335 and R355 OK)
%
%	ex:
%	==
%	chordChromaClasses([atLeast1([[f, #]]), atLeast1([[a, '']]), exactly1([[c, '']]), exactly1([[e, '']])], [f, #], halfdiminished, seven).
%	==
%
%	@param	ClassList		a list of chromaClasses, each of the form functor(ChromaList), where functor is the requirement class (atLeast1, exactly1, or optional), and ChromaList is a list of chromas
%
%	@param	Root			a chroma as in second argument of chord/4
%
%	@param	Quality			as in third argument of chord/4
%
%	@param	Size			as in fourth argument of chord/4

slowChordChromaClasses(ClassList,Root,Quality,Size):-
	chroma(Root),
	chordQuality(Intervals,Quality,Size),
	append([[perfect,octave]],Intervals,IntervalsAndPO),
	list_to_set(IntervalsAndPO,IntervalsAndPONoDupes),
	intervalSort(IntervalsAndPONoDupes,SortedIntervalsAndPONoDupes),
	findall(ChromaClass,
		(member(X, SortedIntervalsAndPONoDupes),
		 interval(Root,Chroma,X), 
		 ( X=[perfect,fifth] -> ChromaClass=optional([Chroma]) ; 
		   consonant(X) -> ChromaClass=atLeast1([Chroma]) ; 
		   ChromaClass=exactly1([Chroma])
		 )
		),
		ClassList),
	length(ClassList,N),
	length(IntervalsAndPO,N). %otherwise if one of the intervals doesn't exist over that root (like an augmented fifth over ax), the class just doesn't make it into ChromaClass

:- abolish(chordChromaClasses/4),forall(slowChordChromaClasses(A,B,C,D),assert(chordChromaClasses(A,B,C,D))),compile_predicates([chordChromaClasses/4]). %cache this heavily used static info

%%	chordChromaSet(-ChromaSet, ?Root, ?Quality, ?Size) is multi.
%
%	find chromas for chord of Root, Quality, Size
%
%	ex:
%	==
%	chordChromaSet([[d, b], [f, ''], [a, '']],[d,b],augmented,triad).
%	==
%
%	@param	ChromaSet		the list of chromas in the chord specified by Root, Quality, Size, without dupes, and ordered by degree
%
%	@param	Root			a chroma as in second argument of chord/4
%
%	@param	Quality			as in third argument of chord/4
%
%	@param	Size			as in fourth argument of chord/4

slowChordChromaSet(ChromaList,Root,Quality,Size):-
	chordChromaClasses(ClassList,Root,Quality,Size),
	findall(X,
		(member(Y,ClassList),
		 Y=..[_,[X]]
		),
		ChromaList).		

:- abolish(chordChromaSet/4),forall(slowChordChromaSet(A,B,C,D),assert(chordChromaSet(A,B,C,D))),compile_predicates([chordChromaSet/4]). %cache this heavily used static info

%%	chordNoteNums(-NumList, ?Chord) is multi.
%
%	find noteClasses for Chord
%
%	these are octave blowouts of the chromas defined in chordChromaClasses/4
%
%	ex:
%	==
%	chordNoteNums(A,[[g,x],halfdiminished,seven]),maplist(writeln,A).
%	atLeast1([45-[[g, x], 1], 57-[[g, x], 2], 69-[[g, x], 3], 81-[[g, x], 4], 93-[[g, x], 5]])
%	atLeast1([48-[[b, #], 1], 60-[[b, #], 2], 72-[[b, #], 3], 84-[[b, #], 4], 96-[[b, #], 5]])
%	exactly1([39-[[d, #], 1], 51-[[d, #], 2], 63-[[d, #], 3], 75-[[d, #], 4], 87-[[d, #], 5]])
%	exactly1([43-[[f, x], 1], 55-[[f, x], 2], 67-[[f, x], 3], 79-[[f, x], 4], 91-[[f, x], 5]])
%	==
%
%	@param	NumList			a list of noteClasses, each of the form functor(NotePairList), where functor is the requirement class (atLeast1, exactly1, or optional), and NotePairList is a list of pairs NoteNum-Note
%
%	@param	Chord			in form [Root, Quality, Size], ala last three arguments of chord/4

slowChordNoteNums(NumList,Chord):-
	Chord=[Root,Quality,Size],
	chordChromaClasses(Cs,Root,Quality,Size),
	findall(X,
		(member(Y,Cs),
		 Y=..[Class,[Chroma]],
		 setof(N-Note,Cs^Ns^(notes_chromas_nums([Note],[Chroma],Ns),member(N,Ns)),ClassNums),
		 X=..[Class,ClassNums]
		),
		NumList
	).

:- abolish(chordNoteNums/2),forall(slowChordNoteNums(A,B),assert(chordNoteNums(A,B))),compile_predicates([chordNoteNums/2]). %cache this heavily used static info

%%	ranges(?Ranges) is det.
%
%	list of voice ranges
%
%	ex:
%	==
%	ranges([[40, 55], [48, 67], [55, 72], [60, 84]]).
%	==
%
%	@param	Ranges			list of [NoteNum, NoteNum] ranges, one for each voice, sorted first on low extreme

slowRanges(Ranges):-
	setof([LoN,HiN],X^Lo^Hi^(voice(X,Lo,Hi),noteNum(Lo,LoN),noteNum(Hi,HiN)),Ranges).

:- abolish(ranges/1),forall(slowRanges(X),assert(ranges(X))),compile_predicates([ranges/1]). %cache this heavily used static info

%%	selectVoicing(?Voicing) is multi.
%
%	generate legal voicings in a random order without repeats
%
%	each time you call selectVoicing, you get a generator of all legal chord voicings, without repeats, in a new random order.  voicings are precached for fast lookup.
%
%	if Voicing is totally uninstantiated, there are about 20k legal voicings
%
%	voicing rules:
%
%		* Notes are all chord tones of a chord and (Notes,Root,Quality,Size) satisfy the requirements in chordChromaClasses/4 (at least one root and one third, perfect fifths optional, exactly one seventh and altered fifth when present)
%		* dissonances over Root and Bass not doubled
%			* need to update to prevent R333, RR33, and R337, ie strictly more roots than anything else when fifth omitted (R335 and R355 OK)
%		* one note for each voice defined by voice/3, in the specified range, without overlap (unison OK.  aldwell schacter and roig-francoli both suggest that brief crossing can be acceptable in inner voices, we may relax this constraint.).
%		* at most one octave between adjacent voices (2 octaves may separate the bottom 2 voices)
%
%	ex:
%	==
%	selectVoicing([A,[c,''],dominant,seven,second]).
%
%	A = [[[g, ''], 1], [[c, ''], 2], [[b, b], 2], [[e, ''], 3]] ;
%	A = [[[g, ''], 1], [[e, ''], 2], [[b, b], 2], [[c, ''], 3]] ;
%	A = [[[g, ''], 1], [[e, ''], 2], [[c, ''], 3], [[b, b], 3]] ;
%	A = [[[g, ''], 1], [[b, b], 2], [[c, ''], 3], [[e, ''], 3]] ;
%	A = [[[g, ''], 2], [[b, b], 2], [[c, ''], 3], [[e, ''], 3]] ;
%	A = [[[g, ''], 1], [[c, ''], 3], [[e, ''], 3], [[b, b], 3]] ;
%	A = [[[g, ''], 2], [[c, ''], 3], [[e, ''], 3], [[b, b], 3]] ;
%	A = [[[g, ''], 1], [[c, ''], 3], [[b, b], 3], [[e, ''], 4]] ;
%	A = [[[g, ''], 2], [[c, ''], 3], [[b, b], 3], [[e, ''], 4]] ;
%	A = [[[g, ''], 1], [[b, b], 2], [[e, ''], 3], [[c, ''], 4]] ;
%	A = [[[g, ''], 2], [[b, b], 2], [[e, ''], 3], [[c, ''], 4]] ;
%	A = [[[g, ''], 1], [[e, ''], 3], [[b, b], 3], [[c, ''], 4]] ;
%	A = [[[g, ''], 2], [[e, ''], 3], [[b, b], 3], [[c, ''], 4]] ;
%	A = [[[g, ''], 1], [[e, ''], 3], [[c, ''], 4], [[b, b], 4]] ;
%	A = [[[g, ''], 2], [[e, ''], 3], [[c, ''], 4], [[b, b], 4]] ;
%	false.
%	==
%
%	@param	Voicing		in form [Notes,Root,Quality,Size,Inversion], where Notes are ordered by noteNum/voice, Root, Quality, and Size are as in the last three arguments of chord/4, and Inversion is as the first arg in chordInversion/2

selectVoicing(V):-
	ground(V)
	-> voicing(_-V)
	; (numVoicings(NumV),
	   randseq(NumV,NumV,Rands), % randseq's docs should specify it will not produce duplicates
	   member(N,Rands),
	   voicing(N-V)
	  ).
	  
slowVoicing(Notes,Root,Quality,Size,Inversion):-
	numVoices(Len),	
	chordNoteNums(NumClasses,[Root,Quality,Size]),
	generate(NumsNotes, NumClasses,Len), %should test NumsNotes for ground and do conforms instead if so %this will allow R-3-R-3, which i don't think is allowed, need to check aldwell schachter, not clear in roig-francoli.  is R-3-3-3 allowed?
	pairs_keys_values(NumsNotes,Nums,Notes),
	inRange(Nums),
	findall(Chroma,(member([Chroma,_],Notes)),Chromas),
	Chromas=[Bass|_],
	intervals(Bass,Chromas,IntervalList),
	exclude(consonant,IntervalList,Dissonances),
	is_set(Dissonances),
	interval(Root,Bass,[_,Degree]),
	chordInversion(Inversion,Degree).

% these get associated with an integer index so that we can use selectVoicing/1 to explore them in a new random order, but still without repeats.  takes about 20 secs to find all 20k voicings.
:- abolish(voicing/1), setof([NotesV,RootsV,QualV,SizeV,InvV],slowVoicing(NotesV,RootsV,QualV,SizeV,InvV),Voicings), length(Voicings,N), numlist(1,N,Ns), assert(numVoicings(N)), pairs_keys_values(Numd, Ns,Voicings), forall(member(I-V,Numd),assert(voicing(I-V))), compile_predicates([voicing/1,numVoicings/1]). %cache this heavily used static info

%%	slowScalesOverChord(-ChromaLists, ?Chord) is multi.
%
%	temporary alias for scalesOverChord/2
 
%%	scalesOverChord(-ChromaLists, ?Chord) is multi.
%
%	find all scales that contain chord tones
% 
%	a scale must be strictly monotonic, with no enharmonic notes and no overlaps ([[proper][http://www.andymilne.dial.pipex.com/Scales.shtml]]?  [[coherent][http://en.wikipedia.org/wiki/Coherence_(music_theory)]]?).
%
%	here, scales are further restricted to contain each whiteKey, possibly inflected with an accidental, exactly once.
%
%	"chord-scale compatibility" is the basis of harmonic substitution and alteration/color in jazz (see levine, jazz theory).
%
%	http://en.wikipedia.org/wiki/Jazz_scale: "[chord is] G-B-D♭-F. An improviser might then choose a scale containing these four notes, such as G whole tone (G-A-B-C♯-D♯-F), G octatonic or "diminished" (G-A♭-B♭-B-C♯-D-E-F), or a mode of either D or A♭ melodic minor ascending (G-A-B-C♯-D-E-F or G-A♭-B♭-C♭-D♭-E♭-F respectively). In each case the scale contains the chord tones G-B-D♭-F and is said to be "compatible" with it. This notion of "chord scale compatibility" marks a fundamental difference between jazz harmony and traditional classical practice."
%
%	normally, this will be cached out to be instantaneous, but it takes about 10 minutes to compute for every possible chord, so this is currently disabled.  use slowScalesOverChord/2 with the same signature instead, and expect it to take about 5 seconds for a single ground Chord.
%
%	to consider: allow any of the chord tones to be enharmonic -- can we prove we get any additional expresivity with this?  it would increase the complexity by 2 orders magnitude...  the question is do you get any new intervalic patterns?  do any of the non-enharmonic collections exclude any pitch class?
%
%	ex:
%	==
%	scalesOverChord(X,[[c,''],major,seven]),maplist(writeln,X).
%	
%	[[a, ], [b, ], [c, ], [d, ], [e, ], [f, ], [g, ]]
%	[[a, ], [b, ], [c, ], [d, ], [e, ], [f, #], [g, ]]
%	[[a, ], [b, ], [c, ], [d, #], [e, ], [f, ], [g, ]]
%	[[a, ], [b, ], [c, ], [d, #], [e, ], [f, #], [g, ]]
%	[[a, ], [b, ], [c, ], [d, b], [e, ], [f, ], [g, ]]
%	[[a, ], [b, ], [c, ], [d, b], [e, ], [f, #], [g, ]]
%	[[a, #], [b, ], [c, ], [d, ], [e, ], [f, ], [g, ]]
%	[[a, #], [b, ], [c, ], [d, ], [e, ], [f, #], [g, ]]
%	[[a, #], [b, ], [c, ], [d, #], [e, ], [f, ], [g, ]]
%	[[a, #], [b, ], [c, ], [d, #], [e, ], [f, #], [g, ]]
%	[[a, #], [b, ], [c, ], [d, b], [e, ], [f, ], [g, ]]
%	[[a, #], [b, ], [c, ], [d, b], [e, ], [f, #], [g, ]]
%	[[a, b], [b, ], [c, ], [d, ], [e, ], [f, ], [g, ]]
%	[[a, b], [b, ], [c, ], [d, ], [e, ], [f, #], [g, ]]
%	[[a, b], [b, ], [c, ], [d, #], [e, ], [f, ], [g, ]]
%	[[a, b], [b, ], [c, ], [d, #], [e, ], [f, #], [g, ]]
%	[[a, b], [b, ], [c, ], [d, b], [e, ], [f, ], [g, ]]
%	[[a, b], [b, ], [c, ], [d, b], [e, ], [f, #], [g, ]]
%	==
%
%	@param	ChromaLists	list of chroma lists, one for each possible scale that contains the chord tones of Chord.  we compute all possible at once because the algorithm would otherwise generate repeats.
%
%	@param	Chord		in form [Root,Quality,Size], ala the last 3 args of chord/4
 
slowScalesOverChord(ChromaLists,[Root,Quality,Size]):-
	chordChromaSet(Cs,Root,Quality,Size),
	intervals(Root,Cs,Ints),
	member([_,fifth],Ints),
	setof(Sorted,R^S^Chromas^(scale(R,S,Chromas),subset(Cs,Chromas),sort(Chromas,Sorted)),ChromaLists).

% :- abolish(scalesOverChord/2),forall(slowScalesOverChord(A,B),assert(scalesOverChord(A,B))),compile_predicates([scalesOverChord/2]). %cache this heavily used static info -- this takes many mins, switch to qsave_program/2

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% end of dynamic section -- predicate order not significant below this point

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% roman numerals

romanBase(1,i).
romanBase(5,v).
romanBase(10,x).
romanBase(50,l).
romanBase(100,c).
romanBase(500,d).
romanBase(1000,m).

romanMax(3999). %have to start putting bars over letters for 4000=[M,Vbar]

powerOfTen(X,F):-
	LX is log10(X),
	F is floor(LX),
	F =:= LX.

romanMore(N,R):-
	findall(X-Y,(romanBase(X,Y),X=<N),L),
	keysort(L,K),
	last(K,V-VR),
	A is N-V,
	roman(A,AR),
	atom_concat(VR,AR,R).

romanLess(N,R):-
	romanBase(A,AR),
	romanBase(V,VR),
	A > V,
	powerOfTen(V,LV),
	LA is log10(A),
	LA =< LV+1,
	N is A-V,
	atom_concat(VR,AR,R).

roman(0,'').
roman(N,R):-
	romanMax(Max),
	between(1,Max,N),
	integer(N),
	L is floor(log10(N)),
	T is floor(N/10^L),
	B is T*10^L,
	(romanLess(B,XL) -> true ; romanMore(B,XL)),
	P is N-B,
	roman(P,Y),
	upcase_atom(XL,X),
	atom_concat(X,Y,R).

romanNumeral(N,R):-
	ground(R) -> once(roman(N,R)) ; roman(N,R).
	
%make sure we get a unique roman numeral for the numbers 0-3999
% :- findall(N-R,roman(N,R),L),pairs_keys_values(L,N,R),is_set(N),is_set(R),romanMax(Max),M is Max+1,length(L,M). % +1 for zero

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% consistency checks

:-whiteKeys(L),pairs_keys_values(L,A,B),is_set(A),member(0,A),is_set(B),maplist(whiteKeyOK,A). %ensure whiteKeys was well defined (pairs_keys_values is in pairs.pl, is_set (check for dupes) is in library(lists).  are these standard across prologs?)
	
:-accidentals(L),is_set(L),member('',L).

typeOK(A):- %a bad name, wish had lambda
	member(A,[perfect,imperfect]).
%try: forall(member(X,Q),member(X,[perfect,imperfect])) 
:-whiteKeys(L),length(L,N),intervalNamesTypes(P),length(P,N),findall(X,member([X,_],P),M),is_set(M),member(fifth,M),findall(X,member([_,X],P),Q),maplist(typeOK,Q).

:-intervalQualities(perfect,X),member(perfect,X),is_set(X).
:-intervalQualities(imperfect,X),member(major,X),is_set(X).

:-findall(IntervalList,chordQuality(IntervalList,_,_),L),findall(X,(member(X,L),member([_,octave],X)),M),length(M,0).   %verify no chord interval lists have any kinds of octaves in them
:-findall(IntervalList,chordQuality(IntervalList,_,_),L),findall(Degree,(intervalNamesTypes(Degrees),member([Degree,_],Degrees),member(B,L),findall(Y,nth0(Y,B,[_,Degree]),X),length(X,Q),Q>1),M),length(M,0).     %verify no chord interval lists have more than one of any degree

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% sample queries (turn into unit tests and some examples to put in the module doc comment)

allSpellingsOfNoteNum(N,L):-
	setof(M,noteNum(M,N),L).

allNoteNums(L):-
	setof([N,Ns],allSpellingsOfNoteNum(N,Ns),L).

allNoteSpellings(L):-
	setof(M,N^allSpellingsOfNote(N,M),L).

allSpellingsOfNote(N,L):-
	setof(A,raiseNumHalfSteps(A,N,0),L).

allSpellingsOfChroma(Co,L):-
	setof(C,O1^O2^raiseNumHalfSteps([C,O1],[Co,O2],0),L).

allChromaSpellings(L):-
	setof(Cs,C^(allSpellingsOfChroma(C,Cs)),L).

/* every defined noteNum with all their spellings
findall(N-A,noteNum(A,N),L),keysort(L,SL),group_pairs_by_key(SL,B),maplist(writeln,B).
*/

/* all modes of major in all keys
findall((C,N,X),(scaleName(S,major),modeName(I,S,N),mode(I,S,L),scale(C,L,X)),B),maplist(writeln,B).
*/

/* all key signatures
findall((C,M),(scaleName(S,major),scale(C,S,X),exclude(natch,X,M)),L),maplist(writeln,L).
*/

/* all intervals, sorted by size
setof((B,A),intervalSize(A,B),L),maplist(writeln,L).
*/

/* all intervals with all possible spellings
findall((N,C)-[A,B],(interval(A,B,C,N)),L),keysort(L,S),group_pairs_by_key(S,G),maplist(writeln,G).
*/

/*non-intervals (would exceed diminised/augmented)
findall([A,B],(chroma(A),chroma(B),\+interval(A,B,_)),L),maplist(writeln,L).
*/

/* all 809 acceptable unique sorted chroma lists of all chords (perfect fifths optional, no doubling of dissonances over root)
findall(X,(chordChromaClasses(Class,Root,Quality,Size),generate(X,Class,4)),L),list_to_set(L,M),length(M,N).
*/

/* all 263 unique sorted chroma sets that are chords, 152 of them have perfect fifths that are omittable, for a total of 415 possible chroma sets
setof((A-C-Q-S),B^(chordChromaSet(B,C,Q,S),sort(B,A)),L),maplist(writeln,L),length(L,N),findall(A,(member(A,L),A=Chromas-Root-Quality-Size,intervals(Root,Chromas,Ints),member([perfect,fifth],Ints)),M),length(M,Q),K is Q+N.
*/

/* all 151 unique sonorities (sorted chroma sets that are not enharmonic), 91 with and 60 without perfect fifths...  
setof(M-C,B^Q^S^(chordChromaSet(B,C,Q,S),sort(B,M)),L),
setof(B,C^X^(member(X-C,L),theory:all_enharmonics(X,B)),M),maplist(writeln,M),length(M,N),
findall(B,(member(X-C,L),intervals(C,X,Ints),member([perfect,fifth],Ints),theory:all_enharmonics(X,B)),M2),list_to_set(M2,M1),maplist(writeln,M1),length(M1,N1),K is N1+N.

%there are 26 sorted chroma sets that are diminished seven chords
setof(M,C^B^(chordChromaSet(B,C,diminished,seven),sort(B,M)),L),maplist(writeln,L),length(L,N).  

%and only 3 diminished seven sonorites
setof(M,C^B^(chordChromaSet(B,C,diminished,seven),sort(B,M)),L),setof(B,X^(member(X,L),theory:all_enharmonics(X,B)),M),maplist(writeln,M),length(M,N).
*/

/* there are no chroma sets that indicate more than one chord root, quality, and size
findall(X,(chordChromaSet(X,R,Q,S),permutation(X,Y),chordChromaSet(Y,R1,Q1,S1),(R\=R1;Q\=Q1;S\=S1)),L),maplist(writeln,L),length(L,N).
*/

/* 20 secs to find 38544 legal voicings (now 19099 after going back to old raiseNumHalfSteps).  use setof because voicings makes dupes, due to generate()'s overlapping choices of requireds and optionals.  why did i used to get 12,936 legal voicings?
profile(setof((A,B,C,D,E),voicing(A,B,C,D,E),L)),maplist(writeln,L),length(L,N).
*/

/* compute complete transition matrix of voicings: expect about 30 hours to compute, but need to add time to write each transition to disk cuz storing them runs out of stack
setof(.,A^B^X^Y^voiceLeadingOK(A,B,X,Y),L),maplist(writeln,L),length(L,N).
*/

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% unit tests using plunit -- due to bug in pldoc, including the begin_tests and end_tests directives below eliminates the orange "undocumented predicates" section of the generated docs...  jan fixed once but it regressed, haven't heard back? see https://mailbox.iai.uni-bonn.de/mailman/public/swi-prolog/2009/000927.html

% :- begin_tests(theory).

test(reverse) :-
        reverse([a,b], [b,a]).

% :- end_tests(theory).

% :- show_coverage(run_tests). %this also does run_tests from library/lists.pl and library/option.pl for some reason...  (run_tests alone does not)
% :- check.
:- module(theory).